【问题标题】:Behind the scenes of Java's BufferedInputStreamJava 的 BufferedInputStream 的幕后花絮
【发布时间】:2018-09-15 06:54:29
【问题描述】:

首先,我理解缓冲作为包装器的概念,例如,FileInuptStream 充当从底层流读取内容(让我们读取场景)的临时容器,在这种情况下 - @987654323 @。

  1. 比如说,有 100 个字节要从流中读取(文件作为源)。
  2. 如果没有缓冲,代码(BufferedInputStreamread 方法)必须进行 100 次读取(一次一个字节)。
  3. 使用缓冲,根据缓冲区大小,代码进行
  4. 假设缓冲区大小为 50。
  5. 因此,代码仅读取缓冲区(作为源)两次以读取文件的内容。
  6. 现在,由于FileInuptStream 是数据(包含100 个字节的文件)的最终来源(虽然被BufferedInputStream 包装),所以它不是必须读取100 次才能读取100 个字节吗?虽然,代码调用了BufferedInputStreamread 方法,但是调用传递给FileInuptStreamread 方法,需要进行100 次读取调用。这是我无法理解的一点。

IOW,虽然被BufferedInputStream 包裹,但底层流(例如FileInputStream)仍然必须一次读取一个字节。那么,缓冲的好处在哪里(不是只需要两次读取调用来缓冲的代码,而是应用程序的性能)?

谢谢。

编辑:

我将此作为后续的“编辑”而不是“评论”,因为我认为它的上下文更适合这里,并且作为 TL;DR 供@Kayaman 和我之间聊天的读者使用。

BufferedInputStreamread 方法说(摘录):

为了方便起见,它 通过反复调用 底层流的读取方法。这种反复阅读继续 直到满足以下条件之一:

The specified number of bytes have been read,
The read method of the underlying stream returns -1, indicating end-of-file, or
The available method of the underlying stream returns zero, indicating that further input requests would block. 

我深入研究了代码,发现方法调用跟踪如下:

  1. BufferedInputStream -> read(byte b[]) 作为一个我想看到缓冲的作用。
  2. BufferedInputStream -> read(byte b[], int off, int len)
  3. BufferedInputStream -> read1(byte[] b, int off, int len) - 私人的
  4. FileInputStream - 读取(字节 b[],int 关闭,int len)
  5. FileInputStream -> readBytes(byte b[], int off, int len) - 私有和原生。来自源代码的方法描述 -

将子数组作为字节序列读取。

BufferedInputStream 中调用read1(上面提到的#4)处于无限for 循环中。它返回上述read 方法描述摘录中提到的条件。

正如我在 OP(#6) 中提到的,调用似乎是由与 API 方法描述和方法调用跟踪匹配的底层流处理的。

问题仍然存在,如果原生 API 调用 - FileInputStreamreadBytes 一次读取一个字节并创建一个包含这些字节的数组以返回?

【问题讨论】:

  • 这太棒了。我说的第一件事是“FileInputStream 不必一次读取一个字节”,方法的名称是readBytes,末尾有一个 S 表示复数。但是您仍然一直说它一次读取一个字节。为什么要讨论“它会这样做吗?” “不,它没有” “但是……如果它这样做了怎么办?”。
  • 我将编辑您的问题以指出您的示例逻辑中有哪些问题。如果您不想要它,我将回滚更改。现在完全错了,也许你对你写的东西感到困惑,这只是你的错误猜测。
  • 1.这是一个方法名称,在内部,它可以对硬盘上的文件进行多次读取(一次一个字节)并创建一个要返回的数组。 2.我没猜,我是按照API方法描述和源代码挖掘去的。
  • 如果底层流(如 FileInputStream)确实提供批量读/写(实际上是由本机调用处理的),那么,如果有错误,请纠正我,BufferedInputStream 只是一个类似帮助器的对象管理内存中的内容。否则,代码必须自己管理标记、重置或任何其他有效机制。
  • 那么现在你终于明白了!?当所有大众媒体、文件系统和其他一切都在块中处理事情时,您的行为是假设事情一次完成 1 个字节。

标签: java buffer bufferedinputstream


【解决方案1】:

底层流(如FileInputStream)仍然需要读取 一次一个字节

幸运的是,这将非常低效。它允许BufferedInputStreamFileInputStream 进行read(byte[8192] buffer) 调用,这将返回一大块数据。

如果您随后想要读取(或不读取)单个字节,它将有效地从BufferedInputStream's 内部缓冲区返回,而不必进入文件级别。所以BI 可以减少我们从文件系统进行实际读取的时间,当这些完成后,即使最终用户只想读取几个字节,它们也会以一种高效的方式完成。

从代码中可以清楚地看出BufferedInputStream.read() 确实直接委托给UnderlyingStream.read(),因为这会绕过所有缓冲。

public synchronized int read() throws IOException {
    if (pos >= count) {
        fill();
        if (pos >= count)
            return -1;
    }
    return getBufIfOpen()[pos++] & 0xff;
}

【讨论】:

  • 如果FileInputStream(及其兄弟)能够返回数据块,缓冲的必要性是什么?因为,重新调整的数组总是在内存中,可以随意处理。
  • 因为如果您确实想一次读取一个字节,您不想执行 100 次文件系统操作。你从....BUFFER 中得到它们!然后,您可以随心所欲地阅读,因为除非需要更多数据,否则文件系统不会受到影响。
  • 我明白这一点。我的意思是 w.r.t 你的回答 - 它允许 BufferedInputStreamFileInputStream 进行 read(byte[8192] buffer) 调用 这将返回一大块数据
  • 如你所说,FileInputStream会返回一大块数据,那么不清楚为什么需要BufferedInputStream
  • 我已经在我的第一条评论中解释了这一点。这不是关于读取块,而是关于最小化对底层流的调用,无论是文件、套接字还是其他。从缓冲区读取速度更快。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-20
  • 2017-05-03
相关资源
最近更新 更多