【问题标题】:Efficient read/write approach with Java NIO使用 Java NIO 的高效读/写方法
【发布时间】:2012-08-17 01:57:01
【问题描述】:

假设我们有向 Selector 注册的 SocketChannel(处于非阻塞模式)以供阅读。假设在 select() 选择器告诉我们该通道已准备好读取并且我们有一些 ByteBuffer 之后。我们想从我们的通道读取一些字节到这个缓冲区(ByteBuffer 在读取之前被清除)。为此,我们使用通道的 read() 方法返回实际读取的字节数。让我们假设这个数字在从通道读取后是正数,并且 ByteBuffer 的方法 hasRemaining() 返回 true。在这种情况下立即尝试从同一频道读取更多内容是否可行? write() 同样的问题。如果 write() 返回正值并且缓冲区的所有内容都没有发送,是否可以立即重试直到 write() 返回零?

【问题讨论】:

  • 只要你处于非阻塞状态,你应该读/写直到返回值为<= 0,这意味着它会阻塞或结束流。顺便说一句,您的频道通常会准备好写入,因此您应该避免使用写入兴趣,因为它会使您的 select 循环旋转。仅当SocketChannel.write 在完成写入数据之前阻塞时才对写入准备感兴趣。
  • @veer 是的,我忘了说这一切都是关于非阻塞模式的。
  • 但是直到返回值 0 但未能完全填满我们的缓冲区,是否真的值得再次尝试立即读取?下一个 read() 返回正值的可能性是否很大?或者正返回值和未完全填充的缓冲区表明我们(很可能)耗尽了内部套接字缓冲区并且应该返回选择?
  • 呃……我想你不明白这里发生了什么。 ByteBuffer.hasRemaining 表示您还有空间来填充缓冲区。 read 是否填充您的缓冲区与从套接字读取所有可用数据不同。
  • @veer 是的,不一样。但正如我怀疑的那样,未能完全填充缓冲区的最可能原因是当前没有更多可用数据可从套接字读取(并且新数据将在“长时间”后可用,因此我们应该返回选择)。我很想知道这在实践中是否真的如此。如果是真的,我们可以更早地停止我们的读取循环并执行更少的 read() 调用(不等待

标签: java nio


【解决方案1】:

如果你得到一个短读的结果,没有阻塞就没有更多的数据可以读取,所以在没有阻塞之前你不能再次读取。否则下一次读取几乎肯定会返回零或 -1。

如果读取填满缓冲区,从该连接的角度来看,继续读取直到它返回

使用大缓冲区。

这也意味着在每次读取之前清除缓冲区是错误的。您应该使用翻转/获取/压缩循环取出数据,然后缓冲区准备好再次读取,并且您不会冒丢失数据的风险。这反过来意味着每个连接都需要一个缓冲区。

【讨论】:

  • 感谢您的回答!提到缓冲区清除只是为了举例 - 澄清缓冲区的初始状态。
【解决方案2】:

这完全取决于数据到达的数据速率以及应用程序的延迟要求。如果您根本不关心延迟,您可能会通过延迟读取兴趣来获得稍高的带宽,直到您怀疑有足够的数据到达以填满您的缓冲区。

不过,你必须小心。延迟读取可能会迫使内核缓冲更多数据,可能会填满其缓冲区,并且必须开始丢弃数据包或以其他方式进行一些流量控制。这不仅会扼杀最后一段的任何好处。

因此,一般来说,您希望尽可能早地阅读。批处理读取的好处充其量是次要的,而潜在的陷阱可能很大。请记住,您看到非完整读取的事实意味着您处理数据的速度比输入数据的速度要快。换句话说,您处于需要消耗 CPU 的状态,因此额外的较小读取的开销基本上是免费的。

【讨论】:

  • 我不完全同意“批量读取的好处最多是微不足道的”——要小心,因为许多小读取会给机械驱动器带来巨大的负担。当您有多个用户时,这会放大,因为驱动器会寻找很多并且只读取小块数据。在同时请求的数量、可用内存和延迟要求之间取得平衡。
  • @Joe:首先,我们谈论的是网络,而不是磁盘。即使是磁盘,磁盘(和操作系统)也有缓存,因此您实际上不会在每个请求上都进行搜索。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-29
  • 1970-01-01
  • 1970-01-01
  • 2013-11-29
相关资源
最近更新 更多