为什么使用BufferedInputStream逐字节读取文件比使用FileInputStream更快?


70

我试图使用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倍以上。远不止于此。那么,这是为什么呢?有可能使此代码更有效(不使用任何外部库)吗?

Answers:


124

在中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的NIOFileChannel类,但是我没有证据支持这一点。


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


1
嗯,我知道,在问之前我应该​​先检查API。因此,它只是一个8K内部缓冲区。这就说得通了。谢谢。至于“更有效”的部分,这不是必需的,但是我认为我的代码可能在某种程度上过于冗余。我想不是。
ZimZim 2013年

13
@ user1007059不客气。请注意,如果您直接使用FileInputStreamread(byte[], int, int)方法,而使用,则byte[>8192]不需要BufferedInputStream将其包装。
Sotirios Delimanolis

@SotiriosDelimanolis何时read()逐字节使用字节以及何时使用read(byte[])字节数组。我认为阅读数组总是更好。那么可以举个例子,在哪里使用read()逐字节或字节 read(byte[])数组。或 BufferedInputStream。?
Asif Mushtaq

@UnKnown没有一个很好的例子。也许第一个字节包含一些有关文件内容或其他元数据的标志。我认为没有人会使用读取整个文件read()
Sotirios Delimanolis,2016年

1
BufferedInputStream当您的代码每次请求读取的字节数少于缓冲区大小(不一定是一个字节)时,@ emily会更快。BufferedInputStream乐观地行动,阅读的内容超出了您的需求,因此,当您回来时,它已经有了下一批。
Sotirios Delimanolis


1

这是由于磁盘访问的成本。假设您将拥有一个大小为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));
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.