为啥使用 BufferedInputStream 逐字节读取文件比使用 FileInputStream 快?

Posted

技术标签:

【中文标题】为啥使用 BufferedInputStream 逐字节读取文件比使用 FileInputStream 快?【英文标题】:Why is using BufferedInputStream to read a file byte by byte faster than using FileInputStream?为什么使用 BufferedInputStream 逐字节读取文件比使用 FileInputStream 快? 【发布时间】:2013-09-07 03:54:56 【问题描述】:

我尝试使用 FileInputStream 将文件读入数组,而一个约 800KB 的文件需要大约 3 秒才能读入内存。然后我尝试了相同的代码,除了将 FileInputStream 包装到 BufferedInputStream 中,它花费了大约 76 毫秒。为什么使用 BufferedInputStream 逐字节读取文件的速度要快得多,即使我仍在逐字节读取它?这是代码(其余代码完全不相关)。请注意,这是“快速”代码。如果你想要“慢”代码,你可以删除 BufferedInputStream:

InputStream is = null;

    try 
        is = new BufferedInputStream(new FileInputStream(file));

        int[] fileArr = new int[(int) file.length()];

        for (int i = 0, temp = 0; (temp = is.read()) != -1; i++) 
            fileArr[i] = temp;
        

BufferedInputStream 快 30 倍以上。远不止于此。那么,为什么会这样,是否有可能使这段代码更高效(不使用任何外部库)?

【问题讨论】:

【参考方案1】:

FileInputStream 中,方法read() 读取单个字节。来自源代码:

/**
 * Reads a byte of data from this input stream. This method blocks
 * if no input is yet available.
 *
 * @return     the next byte of data, or <code>-1</code> if the end of the
 *             file is reached.
 * @exception  IOException  if an I/O error occurs.
 */
public native int read() throws IOException;

这是对使用磁盘读取单个字节的操作系统的本机调用。这是一项繁重的操作。

使用BufferedInputStream,该方法委托给重载的read() 方法,该方法读取8192 字节数量并缓冲它们直到需要它们。它仍然只返回单个字节(但保留其他字节)。通过这种方式,BufferedInputStream 减少了对操作系统的本机调用以从文件中读取。

例如,您的文件是 32768 字节长。要使用FileInputStream 获取内存中的所有字节,您将需要32768 对操作系统的本机调用。使用BufferedInputStream,您将只需要4,无论您将拨打多少read()(仍然是32768)。

至于如何让它更快,你可能想考虑 Java 7 的 NIO FileChannel 类,但我没有证据支持这一点。


注意:如果您直接使用FileInputStreamread(byte[], int, int) 方法,而使用byte[&gt;8192],则不需要BufferedInputStream 包装它。

【讨论】:

啊,我明白了,我应该在询问之前先检查 API。所以它只是一个 8K 内部缓冲区。这就说得通了。谢谢。至于“更高效”的部分,这不是必需的,但我认为我的代码在某些方面可能过于冗余。我猜不是。 @user1007059 不客气。请注意,如果您直接使用FileInputStreamread(byte[], int, int) 方法,则使用byte[&gt;8192] 您将不需要BufferedInputStream 包装它。 @SotiriosDelimanolis 何时使用read() 逐字节以及何时使用read(byte[]) 字节数组。因为我认为阅读数组总是更好。那么你能给我举个例子,在哪里使用read()逐字节或read(byte[])字节数组。或BufferedInputStream.? @UnKnown 没有很好的例子。也许第一个字节包含一些关于文件内容或其他元数据的标志。我认为没有人会使用 read() 阅读整个文件。 @emily BufferedInputStream 当您的代码每次请求读取比缓冲区大小更少的字节(不一定只有一个字节)时会更快。 BufferedInputStream 行动乐观,阅读量超过你的需要,所以当你回来时,它已经有下一批了。【参考方案2】:

这是因为磁盘访问的成本。假设您将拥有一个大小为 8kb 的文件。在没有 BufferedInputStream 的情况下读取这个文件需要 8*1024 次访问磁盘。

此时,BufferedStream 出现了,它充当了 FileInputStream 和要读取的文件之间的中间人。

在一个镜头中,将获得默认为 8kb 的字节块到内存,然后 FileInputStream 将从这个中间人那里读取字节。 这将减少操作的时间。

private void exercise1WithBufferedStream() 
      long start= System.currentTimeMillis();
        try (FileInputStream myFile = new FileInputStream("anyFile.txt")) 
            BufferedInputStream bufferedInputStream = new BufferedInputStream(myFile);
            boolean eof = false;
            while (!eof) 
                int inByteValue = bufferedInputStream.read();
                if (inByteValue == -1) eof = true;
            
         catch (IOException e) 
            System.out.println("Could not read the stream...");
            e.printStackTrace();
        
        System.out.println("time passed with buffered:" + (System.currentTimeMillis()-start));
    


    private void exercise1() 
        long start= System.currentTimeMillis();
        try (FileInputStream myFile = new FileInputStream("anyFile.txt")) 
            boolean eof = false;
            while (!eof) 
                int inByteValue = myFile.read();
                if (inByteValue == -1) eof = true;
            
         catch (IOException e) 
            System.out.println("Could not read the stream...");
            e.printStackTrace();
        
        System.out.println("time passed without buffered:" + (System.currentTimeMillis()-start));
    

【讨论】:

这个例子很好。然而,检查执行时间是绝对不正确的 - 以这种方式进行基准测试。以 JMH 为例进行正确检查。【参考方案3】:

包裹在 FileInputStream 周围的 BufferedInputStream 会以大块的形式从 FileInputStream 请求数据(我认为默认为 512 字节左右。)因此,如果您一次读取 1000 个字符,则 FileInputStream 只需要转到磁盘两次。这样会快很多!

【讨论】:

它可能是platform dependent,但它是8192 on current android 相同,8K,适用于大多数平台。

以上是关于为啥使用 BufferedInputStream 逐字节读取文件比使用 FileInputStream 快?的主要内容,如果未能解决你的问题,请参考以下文章

bufferedinputstream的使用

深入研究BufferedInputStream内幕

BufferedInputStream 中的读取方法

FileInputstream --BufferedInputStream字节流读取使用方法

使用拷贝文件测试(BufferedInputStream,FileInputStream)

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