如何决定从输入流中读取多少字节?

4
我正在尝试从一个InputStream中读取数据。我写了以下代码。
byte[] bytes = new byte[1024 * 32];
                while (bufferedInStream.read(bytes) != -1) {
                    bufferedOutStream.write(bytes);
                }

我不明白的是每次迭代应该读取多少字节?流中包含一个保存在磁盘上的文件。

我在这里读到了一些内容,但我并没有真正理解那篇帖子。


据我理解,你引用的帖子说如果你从磁盘上读取数据,可以使用8 KB到64 KB的大小。如果你的文件大小小于64 KB,你可以通过一次迭代或最多两次来读取它。 - GokcenG
使用缓冲区的原因是它比一次读取一个字节更快。有效的缓冲区大小取决于您要从中复制的内容,例如套接字、文件或USB。512字节到64 KB之间的大小往往是高效的。例如,大于1 MB的大小可能比较慢,而小一些的缓冲区则更快。 - Peter Lawrey
5个回答

9
假设你有一根水管将水流入浴缸。然后你用一个桶从浴缸里取水,再拿到花园里浇灌草坪。浴缸就是缓冲区。当你走过草坪时,缓冲区正在充满,这样当你回来时就有了一桶水。
如果浴缸很小,那么在你提着桶走路时它可能会溢出,导致浪费水资源。如果浴缸很大,则不太可能发生溢出。因此,更大的缓冲区更方便。但是,更大的浴缸成本更高,占用更多空间。
程序中的缓冲区占用内存空间。你不能只因为方便而占用所有可用内存来存储缓冲区。
通常在读取函数中,你可以指定要读取的字节数。因此,即使你的缓冲区很小,你也可以这样做(伪代码):
const int bufsize = 50;
buf[bufsize];
unsigned read;
while ((read = is.read(buf, bufsize)) != NULL) {
   // do something with data - up to read bytes
}

在上面的代码中,bufzise是读入缓冲区的最大数据量。
如果你的读取函数不允许你指定要读取的最大字节数,那么你需要提供一个足够大的缓冲区来接收可能的最大读取量。
因此,最佳缓冲区大小是应用程序特定的。只有应用程序开发人员才会了解数据的特征。例如,水流入浴缸的速度有多快,你能负担多大的浴缸(嵌入式应用程序),你可以多快地从浴缸到花园再回来端桶。

1
当从文件或套接字读取末尾时,大多数情况下读取的大小与缓冲区不匹配。也就是说,你不能忽略它。 - Peter Lawrey
1
@PeterLawrey 确实,我在伪代码中没有太严格,我添加了一个读取计数器 - 这可能是有益的。 - Angus Comber
@AngusComber 我不太理解你所说的比喻,即使你一次读取了很多数据,在这种情况下,底层的“io”应该会自我阻塞,以避免任何数据丢失,对吧? - stdout
@zgulser 如果数据源生成的数据比您处理的速度更快,那么数据源缓冲区将会溢出。 - Angus Comber
@AngusComber 我理解了那部分。我想说的是 - 如果发生这种情况(因为缓冲区已满),我认为IO操作将被阻塞。 - stdout
@zgulser 假设你无法快速读取源的输出,就像屋顶上正在下大雨一样。水槽到排水口的管道被堵住了或者是个非常细的管道。水槽逐渐填满(缓冲区),但它的大小是有限的,并且管道没有足够快地带走水。因此,水槽溢出了。想象一个嵌入式系统向服务器发送数据。如果服务器没有检索数据,则嵌入式系统中的缓冲区将填满,直到以同样的方式溢出。 - Angus Comber

5

这取决于可用内存、文件大小和其他一些因素。最好进行一些测量。

PS:您的代码有误。bufferedInStream.read(bytes) 可能不会填满整个缓冲区,而只是部分填充。此方法会将实际读取的字节数作为结果返回。

byte[] bytes = new byte[1024 * 32];
int size;
while ((size = bufferedInStream.read(bytes)) != -1) {
    bufferedOutStream.write(bytes, 0, size);
}

2

这是我的建议(假设我们只处理输入流而不考虑如何写入输出流):

  1. 如果您的用例不需要高读取性能,请使用FileInputStream。例如:
最初的回答
FileInputStream fileInputStream = new FileInputStream("filePath");
byte[] bytes = new byte[1024];
int size;
while ((size = fileInputStream.read(bytes)) != -1) {
   outputStream.write(bytes, 0, size);
}
  1. 为了获得更好的读取性能,使用BufferedInputStream并坚持使用其默认缓冲区大小,一次只读取一个字节。例如:
最初的回答中提到,为了获得更好的读取性能,可以使用BufferedInputStream,并坚持使用其默认缓冲区大小,一次只读取一个字节。
byte[] bytes = new byte[1];
BufferedInputStream bufferedInputStream = 
                       new BufferedInputStream(fileInputStream("filePath"))
int size;
while ((size = bufferedInputStream.read(bytes)) != -1) {
    outputStream.write(bytes, 0, size);
}
  1. 为了获得更好的性能,尝试调整BufferedInputStream的缓冲区大小并逐个字节读取。例如:

为了提高性能,可以尝试通过调整BufferedInputStream的缓冲区大小并逐个字节读取来实现。示例代码如下:

byte[] bytes = new byte[1];
BufferedInputStream bufferedInputStream = 
                       new BufferedInputStream(fileInputStream("filePath"), 16048)
int size;
while ((size = bufferedInputStream.read(bytes)) != -1) {
    outputStream.write(bytes, 0, size);
}
  • 如果你需要更多的缓存,可以在BufferedInputStream之上使用buffer。例如:
  • byte[] bytes = new byte[1024];
    BufferedInputStream bufferedInputStream = 
                           new BufferedInputStream(fileInputStream("filePath"), 16048)
    int size;
    while ((size = bufferedInputStream.read(bytes)) != -1) {
        outputStream.write(bytes, 0, size);
    }
    

    0

    你基本上有一个指定长度为1024*32的字节容器

    然后,inputStream 将尽可能填充容器,可能是整个容器,在每次迭代中进行迭代,直到它达到文件末尾,当它只填充剩余字节时,它会在下一次迭代(无法读取任何内容的迭代)返回 -1

    所以,你基本上是将输入复制到大小为1024*32字节的输出块中

    希望这可以帮助你理解代码

    顺便说一下,如果输入流少于1024*32,则最后一次迭代,输出将不仅接收文件的最后一部分,还将重复前一次迭代的内容以填充上一次迭代中未填充的字节。


    0

    这个想法不是使用缓冲输入流一次性读取整个文件内容。你可以使用缓冲输入流来读取与bytes[]数组大小相同的字节数。你消耗读取的字节,然后继续从文件中读取更多字节。因此,你不需要知道文件大小就能读取它。

    本文将更有帮助,因为它解释了为什么应该用缓冲输入流包装文件输入流。

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


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接