8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

Java InputStream阻塞读取

Roei Sagi 2月前

149 0

根据 Java api 文档,在 InputStream 中声明的 read() 方法描述如下:如果由于已到达流末尾而没有可用的字节,则值为 -1

根据 Java api文档 read() 中声明的 InputStream 描述如下:

如果由于已到达流末尾而没有可用的字节,则返回值 -1。此方法将阻塞,直到输入数据可用、检测到流末尾或引发异常。

我有一个 while(true) 循环 read() ,当流中没有发送任何内容时,我总是得到 -1。这是意料之中的。

我的问题是什么时候会 read() 阻塞?因为如果它没有收到任何数据,它会返回 -1。我期望阻塞 read() 会等到收到数据。如果您已经到达输入流的末尾,难道不应该 read() 只是等待数据而不是返回 -1 吗?

或者 read() 仅当有另一个线程访问该流而您 read() 无法访问该流时才会阻塞?


这就引出了我的下一个问题。我曾经有一个事件监听器(由我的库提供),当数据可用时它会通知我。当我收到通知时,我会开始 while((aByte = read()) > -1) 循环存储字节。当我在很短的时间内收到两个事件并且并非所有数据都显示出来时,我感到很困惑。似乎只有第二个事件的数据的尾部会显示出来,其余数据都丢失了。

我最终修改了我的代码,这样当我收到事件时,我就开始 if(inputStream.available() > 0) while((aByte = read()) > -1) 存储字节。现在它工作正常,我的所有数据都显示出来。

有人能解释一下这种行为吗? 的 available() 方法 InputStream 据说返回在阻止下一个调用者(流的调用者?)之前可以读取的字节数。 即使我不使用, available() 我也希望第一个事件的读取只会阻止第二个事件的读取,但不会擦除或消耗太多的流数据。 为什么这样做会导致我的数据无法全部显示?

帖子版权声明 1、本帖标题:Java InputStream阻塞读取
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由Roei Sagi在本站《sockets》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 您混淆了流结束和当前没有可用数据。当当前没有可用数据时,它会阻塞。流结束时,它返回 -1。

  • 某些实现的底层数据源 InputStream 可以发出信号,表示已到达流的末尾,并且不会再发送任何数据。在收到此信号之前,此类流上的读取操作可能会阻塞。

    例如, InputStream 中的 Socket 将会阻塞,而不是返回 EOF,直到收到设置了 FIN 标志的 TCP 数据包。当从这样的流中收到 EOF 时,您可以确保已可靠地接收了该套接字上发送的所有数据,并且您将无法再读取任何数据。(另一方面,如果阻塞读取导致异常,则可能丢失一些数据。)

    其他流(例如来自原始文件或串行端口的流)可能缺乏类似的格式或协议来指示不再有可用数据。此类流可以立即返回 EOF(-1),而不是在当前没有可用数据时阻塞。但是,如果没有这样的格式或协议,您无法确定另一端何时发送完数据。


    关于你的第二个问题,听起来你可能遇到了竞争条件。在没有看到相关代码的情况下,我猜问题实际上出在你的“display”方法上。也许第二个通知的显示尝试以某种方式破坏了第一个通知期间完成的工作。

  • 我正在使用 COM 端口的输入流。同样,如果我执行读取的 while 循环,它将始终返回 -1,直到设备(COM 端口的另一端)输出数据。我永远不必重新打开流。所以你的解释对我来说没有意义。设备每次都会重新打开流吗?

  • 嗯,我只使用过 javax.comm,它按预期实现了 InputStream。rxtx 实现可能违反了 InputStream.read() 的约定。

  • 可能是设备的问题。该设备使用的是 COM/Serial 端口,并带有 USB 接口。这是一个奇怪的接口。

  • 不,我不认为显示是问题所在。我只是通过 system.out 流输出流的数据。

  • 如果流已结束,则返回 -1。如果流仍处于打开状态(即套接字连接),但没有数据到达读取端(服务器速度慢、网络速度慢……),则 read() 会阻塞。

    您不需要调用 available()。我很难理解您的通知设计,但除了 read() 本身之外,您不需要任何调用。方法 available() 只是为了方便。

  • 奇怪的是我需要 available() 否则我就得不到所有数据。你能解释一下为什么我需要 available() 吗?

  • 好吧,这有点乱,所以首先让我们澄清一下: InputStream.read() 阻塞与多线程无关。如果您有多个线程从同一个输入流读取数据,并且触发两个彼此非常接近的事件 - 每个线程都试图消费一个事件,那么您就会得到损坏:第一个读取的线程将获得一些字节(可能是所有字节),而当第二个线程被调度时,它将读取其余的字节。如果您计划在多个线程中使用单个 IO 流,则始终要 synchronized() {} 受到一些外部约束。

    其次,如果你可以读取 InputStream 直到得到 -1,然后等待并稍后再次读取,则你使用的 InputStream 实现已损坏! 的契约 InputStream 明确规定, InputStream.read() 只有在没有更多数据可读取时才应返回 -1,因为已经到达流的末尾并且永远不会有更多数据可用 - 就像当你从文件中读取并到达末尾时一样(1).

    “现在没有更多可用数据,请等待,您将获得更多数据”的行为是 read() 阻塞并且直到有可用数据(或引发异常)才返回。

    1. erickson(目前最热门的)答案 的讨论中深入指出的那样 FileInputStream 如果稍后将数据添加到文件中, read() 实现实际上可以读取超过 \'文件末尾\' 的数据,并在 -1 返回 InputStream 实现中唯一这样的情况(或者最坏的情况是 - 非常非常罕见)。如果您知道您使用 FileInputStream 期望您读取的文件添加了其他数据(一个常见的例子是跟踪日志文件),那么您应该考虑到这一点,但除此之外,这只是 API 的缺陷 InputStream 样式阻塞 IO API 并使用 java.io 非阻塞 IO API,那么 java.nio 您会更好
  • 您是否阅读了 @poison 对 @erickson 的回答的评论?我的意思是关于文件的评论:-1 的意思正是文档中所说的:已到达流的末尾,目前没有更多数据。例如,对于 FileInputStream,EOF 仅表示当前已到达文件末尾。当您附加到该文件时,相同的 InputStream 是否会再次提供数据?

  • @Pavel_K,我没有读过那条评论——主要是因为它是在这次讨论 6 年后写的,而且是在另一个答案上(所以没有通知)。话虽如此——他是对的,与我上面的断言不同,-1 并不意味着“永远没有更多数据可用”,只是表示流当前处于结束状态——如果流源以后可以找到更多数据,read() 很可能会再次返回更多数据——这对文件来说确实有意义,但对其他方面则没有意义。例如,当连接关闭时,从 URLConnection 获取的 InputStream 将返回 -1,并且无法重新打开。Process 也是如此。

  • Yuda 2月前 0 只看Ta
    引用 13

    因此,总而言之,我认为这个讨论是值得的(我会用注释更新我的答案),但除了实际文件系统文件的有限使用之外,在 EOF 之后读取没有任何意义,如果你编写一个使用文件系统语义和 InputStream API 的 API - 大多数人都会感到惊讶。如果你想提供一个非阻塞 IO API,如果暂时没有可用数据则返回 - Java 在 java.nio 包中有更好的 API。

  • kv. 2月前 0 只看Ta
    引用 14

    默认情况下,提供的 RXTX InputStream 的行为不符合要求。

    您必须将接收阈值设置为 1 并禁用接收超时:

    serialPort.enableReceiveThreshold(1);
    serialPort.disableReceiveTimeout();
    

    来源: RXTX 串行连接 - 阻塞 read() 的问题

  • 引用 15

    是的!Jbu,不要放弃你的流。我们在这里谈论的是串行通信。对于串行的东西,绝对可以预期在读取时可以/将返回 -1,但仍期望稍后有数据。问题是大多数人习惯于处理 TCP/IP,除非 TCP/IP 断开连接,否则它应该始终返回 0... 那么是的,-1 是有道理的。但是,对于串行,长时间没有数据流,没有“HTTP 保持活动”或 TCP/IP 心跳,或者(在大多数情况下)没有硬件流控制。但链接是物理的,仍然通过“铜”连接,并且仍然完全处于活动状态。

    现在,如果他们说的是正确的,即:串行应该在 -1 上关闭,那么为什么我们必须监视诸如 OnCTS、pmCarroerDetect、onDSR、onRingIndicator 等东西...哎呀,如果 0 表示它在那里,而 -1 表示它不存在,那么就把所有那些检测功能都搞砸了!:-)

    您所面临的问题可能存在于其他地方。

    现在来谈谈具体细节:

    问:“好像只能显示第二个事件的数据的尾部,其余部分都丢失了。”

    答:我猜你处于一个循环中,重复使用相同的 byte[] 缓冲区。第一条消息进来,尚未显示在屏幕/日志/std 输出上(因为你处于循环中),然后你读取第二条消息,替换缓冲区中的第一条消息数据。同样,因为我猜你没有存储你读了多少,然后确保将存储缓冲区偏移上一次读取的数量。

    问:\'我最终修改了我的代码,以便当我收到一个事件时,我会调用 if(inputStream.available() > 0) while((aByte = read()) > -1) 来存储该字节。\'

    答:太棒了……这真是个好东西。现在,您的数据缓冲区位于 IF 语句中,您的第二条消息不会破坏您的第一条消息……好吧,实际上,它可能一开始只是一条较大的消息。但现在,您将一次性读取所有内容,保持数据完整。

    C:\'... 竞争条件 ...\'

    A:啊,老套的替罪羊!竞争条件…… :-) 是的,这可能是竞争条件,事实上很可能是。但是,也可能只是 RXTX 清除标志的方式。清除“数据可用标志”可能不会像预期的那样快。例如,有人知道 read 和 readLine 在清除先前存储数据的缓冲区和重新设置事件标志方面的区别吗?我也不知道。 :-) 我也找不到答案……但是……让我再说几句话。事件驱动编程仍然存在一些缺陷。让我给你一个我最近不得不处理的真实例子。

    • 我得到了一些 TCP/IP 数据,比如说 20 个字节。
    • 因此我收到了“已接收数据的 OnEvent”。
    • 我甚至从 20 个字节就开始‘读取’。
    • 在我读完 20 个字节之前...我又得到了另外 10 个字节。
    • 然而,TCP/IP 看起来要通知我,哦,看到标志仍然处于设置状态,并且不会再通知我。
    • 然而,我读完了 20 个字节(available() 说有 20 个)...
    • ...最后 10 个字节仍保留在 TCP/IP Q 中...因为我没有收到有关它们的通知。

    瞧,通知被错过了,因为标志仍然被设置……即使我已经开始读取字节。如果我读完了字节,那么标志就会被清除,我就会收到接下来 10 个字节的通知。

    这与您现在所发生的情况完全相反。

    所以,是的,使用 IF available() ... 读取返回的数据长度。然后,如果您比较偏执,请设置一个计时器并再次调用 available(),如果那里仍有数据,则读取新数据。如果 available() 返回 0(或 -1),则放松...坐下来...等待下一个 OnEvent 通知。

  • 引用 16

    错了。你违反了 InputStream 契约。根据定义,到达流的末尾意味着没有更多数据可以传来。到达末尾后,你就不能再返回更多数据了。这只是简单的英语。

  • InputStream is just an abstract class ,不幸的是,实现决定了会发生什么。

    如果没有找到任何内容会发生什么情况:

    • 点2

      点3

    • p4

    • p5

    寻求通用解决方案:

    • 如果你将上述任何流包装在 DataInputStream ,那么你就可以使用诸如 readByte , readChar 等方法。 All -1 values are converted to EOFException . (PS: If you perform a lot of small reads, then it's a good idea to wrap it in a BufferedInputStream first)
    • SocketTimeoutException 都存在 EOFException extend IOException ,还有其他几种可能 IOException It is convenient to just check for IOException 's to detect communication issues.

    另一个敏感话题是刷新。就套接字而言,刷新意味着“立即发送”,但就 Serialio 而言,它意味着“丢弃缓冲区”。

  • AKX 2月前 0 只看Ta
    引用 18

    我发现当我设置一个简单的客户端 - 服务器聊天程序时,SocketInputStream.read(byte[] b) 不会阻塞...我必须使用 DataInputStream 来装饰 SocketInputStream,然后使用 DataInputStream.readUTF() 方法...如果没有收到消息,此方法将会阻塞。

  • 引用 19

    @JingHe ,我以前注意到 read(byte[]) 方法也有类似的问题。也许它没有得到很好的实现。庞大的 InputStream 接口的一个怪癖是,并非所有流都以应有的方式实现了所有方法。- 无论如何,我经常使用 int read() 方法逐字节读取,如果将流包装在 BufferedInputStream 中,该方法仍然很高效。在我使用它的应用程序中,我经常必须评估每个字节,因为我需要寻找起始字节和终止字节。

  • @JingHe - 如果您最终会逐字节读取 - 就像我通常做的那样 - 您可以将那些单个字节写入固定大小的缓冲区(例如 byte[] buffer = new byte[1024]),但我个人更喜欢以下构造:buffer = new ByteArrayOutputStream(); buffer.write(byte);。ByteArrayOutputStream 就像一个灵活的缓冲区(它会自动调整大小)。

返回
作者最近主题: