代码之家  ›  专栏  ›  技术社区  ›  James Hay

为什么使用缓冲区读/写流

  •  9
  • James Hay  · 技术社区  · 15 年前

    在阅读有关阅读和写作流的各种问题之后,所有不同的答案都将类似这样的问题定义为正确的方法:

    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);
       } 
    }
    

    两个问题:

    为什么要读写这些小块?

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

    2 回复  |  直到 15 年前
        1
  •  6
  •   Jason Williams    15 年前

    如果您一次读取一个字节,那么您调用的每个字节都有调用函数来读取该字节的开销,以及额外的开销(例如,执行 fileposition += 1 要记住您在文件中的位置,请检查是否已到达文件结尾,依此类推)

    如果读取4000个字节,则开销相同(在上面的示例中,一个函数调用,一个添加(fileposition+=4000),一个检查是否在文件末尾。所以,就日常开支而言,你刚刚使它快了4000倍。(事实上,还有其他成本,所以你不会看到大的收益,但你已经大幅削减了日常开支)

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

    • 文件可能比程序可用的内存大,所以这只会失败。或者它可能太大,以至于你开始使用虚拟内存,这会大大降低速度。因此,将其拆分为较小的块意味着您可以使用较小的固定大小缓冲区来复制无限量的数据。

    • 您的操作系统和设备可能能够同时读写数据(例如,从一个物理磁盘驱动器复制到另一个物理磁盘驱动器)。如果在写入所有数据之前读取了所有数据,那么在开始写入之前,必须等待整个读取过程。但在许多情况下,您可能能够并行地执行这两个操作—因此,在返回并读取下一个块时,读取一个小块并开始“异步”(在后台)写入它。

    • 你得到的回报是递减的。读取4字节而不是1字节的速度可能快4倍。但是,读取4000、40000或400000不会加快速度(实际上,基于上述原因,更大的缓冲区实际上会减慢速度)。

    • 在某些情况下,物理设备使用特定的数据大小(例如每个扇区4096字节、每个缓存线128字节、每个数据包1500字节或CPU总线上8字节(64位))。将数据分成与底层传输/存储机制匹配(或是其倍数)的块可以帮助硬件更高效地处理数据。

    通常,4Kb到128Kb之间的I/O缓冲区最适合大多数情况,您可以将其调到正在执行的特定操作,因此没有适合所有情况的“完美”大小。

    请注意,在大多数I/O情况下,使用的缓冲区很多。例如,从磁盘复制数据时(简单地说),数据从磁盘读取到硬盘中的读缓存(缓冲区),然后通过接口电缆发送到计算机的驱动器控制器,该控制器也可以缓冲数据。然后,它可以通过一个I/O缓冲区传输到RAM中,在那里它被保存,直到程序准备好接收它为止(它甚至可能会在您请求之前提取数据,因为它期望您继续从同一个文件中读取数据,并尝试缓冲数据,这样您就不必等待它了)。然后将其读到缓冲区中并写入。然后它转到另一个I/O缓冲区,发送到驱动器控制器,传递到驱动器,并缓存在写缓存中。最终,硬盘将决定将数据实际存储在其写缓存中,并且您的拷贝将完成-大多数情况发生在后台,因此在程序认为它已完成写入并继续执行其他任务后的几秒钟内,它可能无法完成写入。(这就是在拔出USB驱动器之前必须“安全地取出”USB驱动器的原因-操作系统可能还没有将所有数据实际写入设备,甚至在计算机说您的复制操作完成后的几秒钟内)

        2
  •  4
  •   Preet Sangha    15 年前

    您通常可以选择读写大小。然而,对于特定的体系结构,一些值将更为理想。我不知道这些是什么。我一直倾向于使用我熟悉的功能,比如4K(我用来写驱动程序的NT系统的页面大小)。但是我在用户模式下尝试过更大的尺寸,我从来没有遇到过任何问题。我尽量减少IO调用的次数。

    我的建议是,这些天,如果块的大小非常相同(操作开销与获得的数量之比)或者非常大(IO系统阻塞和饱和),那么块的大小实际上只是非常重要的。

    我认为在任何特殊情况下你都应该

    1. 最小化IO调用数
    2. 如果 真实的 性能是一个问题。