我是不是应该始终将 InputStream 包装为 BufferedInputStream?

Posted

技术标签:

【中文标题】我是不是应该始终将 InputStream 包装为 BufferedInputStream?【英文标题】:Should I always wrap an InputStream as BufferedInputStream?我是否应该始终将 InputStream 包装为 BufferedInputStream? 【发布时间】:2011-02-27 04:30:32 【问题描述】:

当我知道给定的 InputStream 是否不是缓冲的东西时,总是将 InputStream 包装为 BufferedInputStream 是否有意义?例如:

InputStream is = API.getFromSomewhere()
if(!(is instanceof BufferedInputStream))
  return new BufferedInputStream(is);
return is;

【问题讨论】:

【参考方案1】:

当我知道给定的 InputStream 是否不是缓冲的东西时,总是将 InputStream 包装为 BufferedInputStream 是否有意义?

没有。

如果您可能执行大量小读取(一次一个字节或几个字节),或者如果您想使用缓冲 API 提供的一些更高级别的功能,这很有意义;例如BufferedReader.readLine() 方法。

但是,如果您只打算使用read(byte[]) 和/或read(byte[], int, int) 方法执行大块读取,则将InputStream 包装在BufferedInputStream 中并没有帮助。

(针对@Peter Tillman 对他自己的答案的评论,块读取用例绝对代表了InputStream 类的使用的 0.1% 以上!!但是,他是正确的,因为它通常是 无害在不需要时使用缓冲的 API。)

【讨论】:

【参考方案2】:

我不会那样做,我会把它留在可能的最高抽象级别。如果您不打算使用 BufferedStream 的标记和重置功能,为什么还要包装它?

如果消费者需要它,最好把它包装在那里。

【讨论】:

这似乎暗示标记和重置是BufferedInputStream 在普通InputStream 上添加的唯一有用的东西。从 API 的角度来看,这可能是正确的,但正如其他人所说,BufferedInputStream 会为您处理缓冲读取。从 FileInputStream 中读取一个字节的速度比从 BufferedInputStream 中的读取速度慢 40 倍。也就是说,返回InputStream 并保留您的方法签名。用户可以根据需要包装。 我同意,从性能的角度来看,最好将其包装在 99.9% 的情况下。然而,它确实减轻了消费者思考如何使用 InputStream 的责任。消费者的这些假设限制了可重用性。 我认为在超过 0.1% 的情况下,消费者不会一次读取一个字节,而是自己使用某种缓冲区,在这种情况下 BufferedInputStream 是无用的开销。 @Michael,我认为 Peter 的观点是,在 99% 的情况下,它会比逐字节读取更快,而不是 99% 的时间将它用作字节-按字节读取。【参考方案3】:

您可能并不总是需要缓冲,因此,答案是否定的,在某些情况下它只是开销。

“否”还有另一个原因,而且可能更严重。 BufferedInputStream(或BufferedReader)在与网络套接字一起使用时,如果您还启用了套接字超时,可能会导致不可预知的故障。读取数据包时可能会发生超时。您将不再能够访问传输到该点的数据 - 即使您知道有一些非零字节数(请参阅java.net.SocketTimeoutException 这是java.io.InterruptedIOException 的子类bytesTransferred 变量可用)。

如果您想知道在读取时如何发生套接字超时,只需考虑调用 read(bytes[]) 方法,包含消息的原始数据包最终被拆分,但其中一个部分数据包延迟超过超时(或超时的剩余部分)。当再次包装在实现 java.io.DataInput 的东西中时,这种情况可能会更频繁地发生(对多字节值的任何读取,例如 readLong()readFully()BufferedReader.readLine() 方法。

请注意,java.io.DataInputStream 对于有超时的套接字流也是一个不好的候选,因为它在超时异常时也表现不佳。

【讨论】:

关于 BufferedInputStream 和 BufferedReader 这是都市神话。如果您遇到读取超时,(i)您正在阅读,因此内部缓冲区是空的,否则您将不会阅读; (ii) 在超时期限内没有数据到达。因此,不会丢失任何数据。试试看。 @EJP:你让我真正想到了这个,但我仍然认为这可能是个问题。当缓冲流确实需要执行 I/O(填充缓冲区)时,您可以获得超时异常并且内部变量会跟踪缓冲区中的多少字节不会被更新。我试图对此进行测试,但是,尽管我可以复制超时异常,但我似乎无法复制bytesTransferred 非零的情况。在那之前,我无法以一种或另一种方式证明这一点。 [我丢失了 DataInputStream 和超时的数据。] @EJP:也许从套接字读取字节数组永远不会由于超时而导致部分读取缓冲区,然后bytesTransferred 永远不会非零(否则 BufferredInputStream 会失败)。这也可能是不同平台/供应商上的 JVM 实现可能会产生不同结果的情况——我刚刚在 Windows 7 上使用 Sun/Oracle Java 进行了测试。 但是 BufferedInputStream 不会“填充缓冲区”。请参阅 Javadoc。它读取任何要读取的内容,就像任何其他读取一样,并返回该长度。具体来说,它永远不会阻塞两次。如果在超时期限内有任何数据到达,则没有超时。相反,如果超时,则没有数据到达。所以不会丢失任何东西。就 DataInputStream 而言,问题是真实存在的。至于 BufferedInputStream,没有。【参考方案4】:

这还取决于您将如何从 InputStream 中读取数据。如果您要一次读取一个字符/字节(即 read()),那么 BufferedInputStream 将通过代表您安静地进行批量读取来减少您的开销。如果您打算一次将一个块读入 4k 或 8k 字节/字符数组,那么 BuffredInputStream 可能不会让您受益。

【讨论】:

以上是关于我是不是应该始终将 InputStream 包装为 BufferedInputStream?的主要内容,如果未能解决你的问题,请参考以下文章

如何查看 InputStream 中的前两个字节?

用 InputStream 包装 ByteBuffer

我是不是应该始终将 CancellationToken 添加到我的控制器操作中?

您是不是应该始终对 C 中的数字使用“int”,即使它们是非负数?

您是不是应该始终对 C 中的数字使用“int”,即使它们是非负数?

使用 BufferedInputStream 包装后对原始 InputStream 的影响