为什么要分块写入流?

6
我想知道为什么在许多示例中,字节数组会被分块读入流中,而不是一次性全部读取...我知道这是一个含糊的问题,但我很感兴趣。
我对硬件有一些了解,填充缓冲区可能非常依赖大小,你不希望再写入缓冲区之前它已经被刷新到需要去的地方等等...但在 .Net 平台(和其他现代语言)中,我看到了两者的示例。那么何时使用哪种方法,或者第二个绝对不行?
以下是我所说的代码:
var buffer = new byte[4096];

while (true)
{
    var read = this.InputStream.Read(buffer, 0, buffer.Length);

    if (read == 0)
        break;

    OutputStream.Write(buffer, 0, read);
}

而不是:

var buffer = new byte[InputStream.Length];

var read = this.InputStream.Read(buffer, 0, buffer.Length);

OutputStream.Write(buffer, 0, read);

我相信两者都是合法的,所以为什么要费尽周折使用while循环(无论你决定如何结构化它)?
我在这里充当魔鬼的代言人,因为我想尽可能地学习 :)
4个回答

20

在第一种情况下,你只需要4kB的内存。而在第二种情况下,你需要与输入数据流大小相同的内存。如果输入流为4GB,则需要4GB的内存。

如果文件复制操作需要4GB的RAM,这会是好事吗?如果你要准备一个20GB的磁盘映像呢?

还有一件事就是使用管道。你在Windows上不经常使用它们,但在其他操作系统上经常看到类似情况。第二种情况等待所有数据被读取,然后才将它们写入输出流。然而,有时尽快写入数据是明智的——第一种情况将在读取输入的前4kB时开始向输出流写入。想象一下服务网页:建议Web服务器尽快发送数据,以便客户端Web浏览器开始渲染标题和内容的第一部分,而不是等待整个主体。

但是,如果你知道输入流不会超过4kB,那么两种情况是等效的。


1
一般情况下,你在内存中保存的数量更为重要,因此如果你正在填充一个缓冲区(流)而不移动它,那就不好了。比如说,如果我们将OutputStream排除在外,只用while循环填充InputStream呢?因为我也见过这种情况,那样做和第二个例子一样糟糕吗? - tigerswithguitars
1
这完全取决于你的具体情况和你想要做什么。有些算法可以处理小块数据(如计算值的总和、查找最大值),而有些算法需要全部数据(例如排序)。在第二种情况下,读取所有数据是必要的。但在第一种情况下,不是必须的。 - liori
很酷......所以它是应用程序特定的,这是我的主要想法,我认为......而不是大多数语言中对象本身的性质。谢谢 :) - tigerswithguitars

6
有时候,InputStream.Length对某些来源是无效的,例如从网络传输中获得,或者缓冲区可能非常巨大,例如从一个巨大的文件中读取。在我看来。

这是一个非常好的观点...我没有考虑过那种可能性。但是这很有道理,特别是如果你接近硬件并且正在读取缓冲区接收信息! - tigerswithguitars
+1...希望它可以是+2。我想接受这个答案,因为你以一种非常简单的方式提出了我根本没有想到过的东西,这总是很酷的。但考虑到SO社区的利益,最好接受对大多数人有用的答案。 - tigerswithguitars

2
它能保护您免受输入流长度为几个GB的情况的影响。

什么是保护?为什么需要它? - tigerswithguitars
例如,保护免受OutOfMemoryException的影响。 - Joe
好的。如果你将一个比应用程序访问权限更高的文件读入内存,那么就会出现这种情况。但是,对于任何大量数据来说,这都是可能的。因此,模式的粗糙程度并不能保护它,只有缓冲区刷新到输出流和回收才能解决问题。 - tigerswithguitars
@tigerswithguitars - “所以这个模式的粗糙度并没有保护它”- 当然有。在您的示例中,您只需要配置大小(4096)的缓冲区即可,而不是大小与输入流一样大的缓冲区。 - Joe
很酷,但你只会得到你想要的整个文件的一部分,对吧?所以你必须进行一些实际的软件工程,并确保这不会导致世界崩溃(ed-应用程序死亡)!:P - tigerswithguitars
@tigerswithguitars,“但你只会得到你想要的整个文件的一部分吗” - 不,你将一次从输入流复制整个文件到输出流中的一个块。 顺便说一句,使用.NET 4或更高版本,您可以简单地使用“InputStream.CopyTo(OutputStream)”,它使用默认缓冲区大小,或者例如“InputStream.CopyTo(OutputStream,4096)”,它允许您指定缓冲区大小。 - Joe

2

如果你不知道Read可能返回多少数据,这可能会在读取非常大的文件时导致严重的性能问题。

如果你可以控制输入,并确认其大小是合理的,则可以一次性读取整个数组。但是,如果用户可以提供任意输入,请特别小心。


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