是什么让没有缓冲区的文件读取如此昂贵?

时间:2017-02-04 01:08:13

标签: java performance io inputstream

最近,我创建了一个界面,强制用户使用默认方法实现单个fromStream(OutputStream),如下所示:

public default T fromFile(File file) throws IOException {
    try (InputStream stream = new FileInputStream(file)) {
        return fromStream(stream);
    }
}

由于单个字节直接从FileInputStream读取,因此很快就发现这非常昂贵(每MB几秒)。

将其包裹在BufferedInputStream解决了我的问题,但它让我想到为什么FileInputStream非常昂贵。

文件通道在读取字节时不会关闭或打开,那么为什么首先需要缓冲区呢?

2 个答案:

答案 0 :(得分:3)

如果使用read()方法从无缓冲流中读取字节,JVM将最终向操作系统重复读取系统调用,以从文件中读取单个字节。 (在引擎盖下,JVM可能会将read(addr, offset, count)调用为1。)

进行系统调用的成本很高。比常规方法调用至少高出几个数量级。这是因为在:

中存在很大的开销
  • 在应用程序(非特权)安全域和系统(特权)安全域之间切换上下文。需要保存寄存器集,需要更改虚拟内存映射,需要刷新TLB条目等。
  • 操作系统必须做各种额外的事情以确保系统调用所请求的是合法的。在这种情况下,OS必须在给定当前文件位置和大小,地址是否在应用程序的地址空间内并且映射为可写的情况下确定所请求的偏移和计数是否正常。等等。

相比之下,如果您使用缓冲流,则流将尝试以大块的形式从操作系统中读取文件。这通常会导致系统调用数量减少数千倍。

实际上,这不是关于文件如何存储在磁盘上的。确实,数据最终必须一次读取一个块,等等。但是,操作系统足够聪明,可以自行缓冲。它甚至可以预读文件的一部分,以便它们在(内核)内存中为应用程序做好准备,同时让系统调用来读取它们。

多个单字节read()调用极不可能导致额外的磁盘流量。唯一合适的情况是,如果您在每个read()之间等待很长时间......并且操作系统会重用它缓存磁盘块的空间。

答案 1 :(得分:1)

当您从文件中读取时,您必须一次读取一个块,因为这是硬件支持的唯一数量。如果你在没有缓冲的情况下一次读取一个字符,那么假设512B块,你将读取相同的块512次以读取整个块。如果你读取并缓冲,你将访问磁盘,然后从内存中读取。

访问磁盘比访问内存慢几个数量级,这不是一个好主意。