为啥使用 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
类,但我没有证据支持这一点。
注意:如果您直接使用FileInputStream
的read(byte[], int, int)
方法,而使用byte[>8192]
,则不需要BufferedInputStream
包装它。
【讨论】:
啊,我明白了,我应该在询问之前先检查 API。所以它只是一个 8K 内部缓冲区。这就说得通了。谢谢。至于“更高效”的部分,这不是必需的,但我认为我的代码在某些方面可能过于冗余。我猜不是。 @user1007059 不客气。请注意,如果您直接使用FileInputStream
的read(byte[], int, int)
方法,则使用byte[>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 快?的主要内容,如果未能解决你的问题,请参考以下文章
FileInputstream --BufferedInputStream字节流读取使用方法