2010-05-12 101 views
9

继读出存储的各种问题和写入数据流,所有不同的答案定义是这样的正确的方式做到这一点:为什么要使用缓冲区读取/写入流

private void CopyStream(Stream input, Stream output) 
{ 
    byte[] buffer = new byte[16 * 1024]; 
    int read; 
    while ((read = input.Read(buffer, 0, buffer.Length)) > 0) 
    { 
     output.Write(buffer, 0, read); 
    } 
} 

两个问题:

为什么要在这些小块中读写?

使用的缓冲区大小的意义是什么?

回答

6

如果你读一次一个字节,然后每次调用字节有调用该函数读取字节的开销和附加费用(例如,做一个fileposition += 1记住该文件在你,检查你是否已经到达文件末尾,等等)

如果你读4000个字节,那么你有相同的开销(在上面的例子中,1个函数调用,一个add(fileposition + = 4000) ,并检查一下你是否在档案末尾,所以就费用而言,你已经快了4000倍(实际上,还有其他的成本,所以你不会看到那么大的一个收益,但你已经大幅削减开销)

当然,您可以创建一个与整个文件一样大的缓冲区,并获得绝对最低的开销。然而:

  • 该文件可能很大 - 比您的程序可用的内存大,所以这只会失败。或者它可能太大,你开始使用虚拟内存,这将大大减慢速度。所以把它分成小块意味着你可以使用一个小的固定大小的缓冲区拷贝数据无限量的

  • 您的操作系统和设备可能能够同时读写数据(例如,从一个物理磁盘驱动器复制到另一个)。如果在写入所有数据之前读取所有数据,则必须等待整个读取过程,然后才能开始写入。但在很多情况下,你可能可以同时进行两种操作 - 所以读一个小块并开始写“异步”(在后台),而你回去读下一个块。

  • 您的收益递减。读取4个字节而不是1个可能会快4倍。但是阅读4000,40000或400,000不会加快速度(事实上,由于上​​述原因,较大的缓冲区实际上可能会减慢速度)。在某些情况下,物理设备使用特定的数据大小(例如,每个扇区4096个字节,每个高速缓存行128个字节,或每个数据包1500个字节,或CPU总线上的8个字节(64个位))。将数据划分成与底层传输/存储机制相匹配(或成倍数)的块可以帮助硬件更有效地处理数据。

典型的I/O 128KB的4kB的工作之间最适合大多数情况下的缓冲区,你可以调整这些在特定的操作执行,所以有一个适合所有情况的“完美”的大小。

请注意,在大多数I/O情况下,正在使用许多缓冲区。例如从磁盘复制数据时(简单地说),它从磁盘读取到硬盘驱动器中的读取缓存(缓冲区),然后通过接口电缆发送到计算机的驱动器控制器,这也可以缓冲数据。然后它可以通过一个I/O缓冲区传输到RAM中,直到你的程序准备好接收它(它可能会在你请求之前提取数据,因为它期望你继续读取数据相同的文件,并试图缓冲数据,所以你不必等待它)。然后你将它读入你的缓冲区并写下来。然后它进入另一个I/O缓冲区,发送到驱动器控制器,传递到驱动器,并缓存在写入缓存中。最终,硬盘驱动器会决定将数据实际存储在其写入缓存中,并且您的副本将完成 - 其中大部分发生在后台,因此在您的程序认为完成写入之后的几秒钟之内可能无法完成写入,已经开始另一项任务。 (这就是为什么在拔出USB驱动器之前必须“安全地移除”USB驱动器的原因 - 操作系统可能尚未将所有数据实际写入设备,甚至在计算机表示复制操作完成之后几秒钟)

4

您通常可以选择读取和写入的大小。但是,对于特定架构,某些值将更加优化。这些都是,超出我的知识。我一直倾向于坚持我熟悉的fgures,比如4K(在我使用的写驱动程序的NT系统上的页面大小)。但是,在用户模式下进行了大尺寸的实验,我从未遇到任何问题。我尽量保持IO呼叫的数量尽可能低。

我的建议是,如果块大小非常小(操作开销与数量增加)或非常大(IO系统阻塞和饱和),那么块大小实际上只是重要的。

我想对于任何特定的情况下,你应该

  1. 最小化IO调用数
  2. 改变这个策略,如果真正性能是一个问题。
相关问题