2017-02-04 78 views
0

最近,我创建了强制用户界面实现单fromStream(OutputStream)使用它的默认方法看起来像这样:什么让文件读取没有缓冲区如此昂贵?

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

后不久事实证明,这是非常昂贵(每MB几秒钟)由于单字节被直接从FileInputStream读取。

将其包装在BufferedInputStream解决了我的问题,但它给我留下了为什么FileInputStream是如此非常昂贵的问题。

文件频道未关闭或读取字节时打开,所以为什么有需要摆在首位的缓冲区?

回答

3

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

使系统调用的开销很大。至少比常规方法调用多几个数量级。这是因为有显著开销:

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

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


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

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

1

当你从文件中读取,你必须在一个时间来阅读它的模块,因为这是硬件支持只的数量。如果你读一次一个字符没有缓冲,则假设512B块,你会读出同一块512次读取整个块。如果您读取和缓冲,您将访问磁盘一次,然后从内存中读取。

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

+0

那么,一个非常差的操作系统可以这样做,但每个具有Java实现的操作系统都具有内部OS文件系统缓存,用于缓冲磁盘中的数据块,因此该块将不会再被读取。但即使从文件系统缓存中读取,也需要系统调用,与处于用户进程内部的任何内容相比,这会带来很大的开销。见Stephen的回答。 –