【问题标题】:Best way to read a large file into a byte array in C#?在 C# 中将大文件读入字节数组的最佳方法?
【发布时间】:2011-01-03 02:28:48
【问题描述】:

我有一个网络服务器,它将大型二进制文件(几兆字节)读入字节数组。服务器可能同时读取多个文件(不同的页面请求),因此我正在寻找最优化的方法来执行此操作,而不会过多地占用 CPU。下面的代码够好吗?

public byte[] FileToByteArray(string fileName)
{
    byte[] buff = null;
    FileStream fs = new FileStream(fileName, 
                                   FileMode.Open, 
                                   FileAccess.Read);
    BinaryReader br = new BinaryReader(fs);
    long numBytes = new FileInfo(fileName).Length;
    buff = br.ReadBytes((int) numBytes);
    return buff;
}

【问题讨论】:

  • 你的例子可以简写为byte[] buff = File.ReadAllBytes(fileName)
  • 为什么它是第三方网络服务意味着文件在发送到网络服务之前需要完全在 RAM 中,而不是流式传输?网络服务不会知道其中的区别。
  • @Brian,有些客户端不知道如何处理 .NET 流,例如 Java。在这种情况下,所有可以做的就是读取字节数组中的整个文件。
  • @sjeffrey:我说数据应该是流式传输的,而不是作为 .NET 流传输的。无论哪种方式,客户都不会知道其中的区别。

标签: c# .net bytearray binary-data


【解决方案1】:

只需将整个内容替换为:

return File.ReadAllBytes(fileName);

但是,如果您担心内存消耗,您应该一次将整个文件全部读入内存。你应该分块做。

【讨论】:

  • 此方法仅限于 2^32 字节文件 (4.2 GB)
  • File.ReadAllBytes 抛出 OutOfMemoryException 与大文件(使用 630 MB 文件测试但失败)
  • @juanjo.arana 是的,嗯......当然总会有一些不适合记忆的东西,在这种情况下,这个问题没有答案。通常,您应该流式传输文件,而不是将其完全存储在内存中。您可能想看看这个权宜之计:msdn.microsoft.com/en-us/library/hh285054%28v=vs.110%29.aspx
  • .NET 中的数组大小有限制,但在 .NET 4.5 中,您可以使用特殊配置选项打开对大型数组 (> 2GB) 的支持,请参阅 msdn.microsoft.com/en-us/library/hh285054.aspx
  • 对于大文件读取,这不应该是公认的或评价最高的答案,至少给出的代码是这样。 “您不应该一次将整个文件全部读入内存。您应该分块执行”这句话是正确的,并且应该得到代码的支持。在该部分得到纠正之前投反对票,因为这个答案的代码非常具有误导性,并且与那个非常正确的陈述相矛盾。
【解决方案2】:

我会这样想:

byte[] file = System.IO.File.ReadAllBytes(fileName);

【讨论】:

  • 请注意,当获得非常大的文件时,这可能会停止。
【解决方案3】:

您的代码可以考虑到这一点(代替 File.ReadAllBytes):

public byte[] ReadAllBytes(string fileName)
{
    byte[] buffer = null;
    using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
    {
        buffer = new byte[fs.Length];
        fs.Read(buffer, 0, (int)fs.Length);
    }
    return buffer;
} 

注意 Integer.MaxValue - Read 方法设置的文件大小限制。换句话说,你一次只能读取一个 2GB 的块。

还要注意 FileStream 的最后一个参数是缓冲区大小。

我还建议阅读FileStreamBufferedStream

一如既往,一个简单的示例程序来分析最快将是最有益的。

您的底层硬件也会对性能产生很大影响。您是否正在使用带有大缓存的基于服务器的硬盘驱动器和带有板载内存缓存的 RAID 卡?或者您使用的是连接到 IDE 端口的标准驱动器?

【讨论】:

  • 为什么硬件类型会有所不同?因此,如果是 IDE,您使用一些 .NET 方法,如果是 RAID,您使用另一种方法?
  • @Tony_Henrich - 它与您从编程语言中发出的调用无关。有不同类型的硬盘驱动器。例如,Seagate 硬盘被分类为“AS”或“NS”,其中 NS 是基于服务器的大型缓存硬盘,而“AS”硬盘是基于消费类家用计算机的硬盘。搜索速度和内部传输速率也会影响您从磁盘读取内容的速度。 RAID 阵列可以通过缓存极大地提高读/写性能。因此,您也许可以一次读取所有文件,但底层硬件仍然是决定因素。
  • 此代码包含一个严重错误。读取只需要返回至少 1 个字节。
  • 我会确保将 long 转换为 int 的检查结构如下所示:checked((int)fs.Length)
  • 我只会在 var binaryReader = new BinaryReader(fs); fileData = binaryReader.ReadBytes((int)fs.Length); 声明中使用 using。但这实际上就像 OP 所做的那样,只是我通过将 fs.Length 转换为 int 而不是获取 FileInfo 长度的 long 值并将其转换来剪切一行代码。
【解决方案4】:

使用 C# 中的 BufferedStream 类来提高性能。缓冲区是内存中用于缓存数据的字节块,从而减少了对操作系统的调用次数。缓冲区提高了读写性能。

有关代码示例和其他说明,请参见以下内容: http://msdn.microsoft.com/en-us/library/system.io.bufferedstream.aspx

【讨论】:

  • 当您一次阅读整本书时,使用BufferedStream 有什么意义?
  • 他要求最好的性能不要一次读取文件。
  • 性能在操作的上下文中是可衡量的。对于您正在按顺序读取的流的额外缓冲,一次全部写入内存不太可能从额外的缓冲区中受益。
【解决方案5】:

我可能会争辩说这里的答案一般是“不要”。除非您绝对需要一次获取所有数据,否则请考虑使用基于 Stream 的 API(或阅读器/迭代器的某些变体)。当您有多个并行操作(如问题所建议的那样)以最小化系统负载并最大化吞吐量时,这一点尤其重要。

例如,如果您将数据流式传输给调用者:

Stream dest = ...
using(Stream source = File.OpenRead(path)) {
    byte[] buffer = new byte[2048];
    int bytesRead;
    while((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0) {
        dest.Write(buffer, 0, bytesRead);
    }
}

【讨论】:

  • 添加到您的陈述中,如果您有 I/O 绑定操作(例如将文件流式传输到客户端),我什至建议考虑使用异步 ASP.NET 处理程序。但是,如果您出于某种原因必须将整个文件读取到byte[],我建议避免使用流或其他任何东西,而只需使用系统提供的 API。
  • @Mehrdad - 同意;但完整的背景尚不清楚。同样,MVC 对此也有操作结果。
  • 是的,我一次需要所有数据。它将转到第三方网络服务。
  • @Tony:我在回答中说:File.ReadAllBytes
  • @iGod 每次更改offset 以增加您读取的字节数,并将每次读取的数量减少相同的数量(以bytesToRead = target.Length 开头);所以:int offset = 0; int toRead = target.Length; while((bytesRead - source.Read(target, offset, toRead)) > 0) { offset += bytesRead; toRead -= bytesRead; }
【解决方案6】:

根据操作频率、文件大小以及您正在查看的文件数量,还有其他性能问题需要考虑。要记住的一件事是,您的每个字节数组都将在垃圾收集器的支配下释放。如果您没有缓存任何这些数据,您最终可能会产生大量垃圾并将大部分性能损失给% Time in GC。如果块大于 85K,您将分配到大对象堆(LOH),这将需要所有代的集合来释放(这是非常昂贵的,并且在服务器上将停止所有执行,同时它正在进行)。此外,如果 LOH 上有大量对象,最终可能会出现 LOH 碎片(LOH 永远不会压缩),这会导致性能不佳和内存不足异常。达到某个点后,您可以循环使用该过程,但我不知道这是否是最佳做法。

关键是,您应该考虑应用程序的整个生命周期,然后才必须以最快的方式将所有字节读入内存,否则您可能会以短期性能换取整体性能。

【讨论】:

  • 关于它的源代码C#,用于管理garbage collectorchunks性能,事件计数器,...
【解决方案7】:

我建议尝试使用Response.TransferFile() 方法,然后使用Response.Flush()Response.End() 来提供大文件。

【讨论】:

    【解决方案8】:

    如果您正在处理超过 2 GB 的文件,您会发现上述方法都失败了。

    将流传递给MD5 并允许它为您分块文件要容易得多:

    private byte[] computeFileHash(string filename)
    {
        MD5 md5 = MD5.Create();
        using (FileStream fs = new FileStream(filename, FileMode.Open))
        {
            byte[] hash = md5.ComputeHash(fs);
            return hash;
        }
    }
    

    【讨论】:

    • 我看不出代码与问题(或您在书面文本中的建议)有何关联
    【解决方案9】:

    我想说BinaryReader 很好,但可以重构为这个,而不是所有那些用于获取缓冲区长度的代码行:

    public byte[] FileToByteArray(string fileName)
    {
        byte[] fileData = null;
    
        using (FileStream fs = File.OpenRead(fileName)) 
        { 
            using (BinaryReader binaryReader = new BinaryReader(fs))
            {
                fileData = binaryReader.ReadBytes((int)fs.Length); 
            }
        }
        return fileData;
    }
    

    应该比使用.ReadAllBytes() 更好,因为我在顶部响应的 cmets 中看到包括 .ReadAllBytes() 的评论者之一对大于 600 MB 的文件有问题,因为 BinaryReader 是针对这种类型的的事情。此外,将其放入 using 语句可确保 FileStreamBinaryReader 被关闭和处置。

    【讨论】:

    • 对于 C#,需要使用“using (FileStream fs = File.OpenRead(fileName))”而不是“using (FileStream fs = new File.OpenRead(fileName))”,如上所示。刚刚在 File.OpenRead() 之前删除了 new 关键字
    • @Syed 上面的代码是为 C# 编写的,但你说得对,那里不需要 new。已删除。
    【解决方案10】:

    使用这个:

     bytesRead = responseStream.ReadAsync(buffer, 0, Length).Result;
    

    【讨论】:

    • 欢迎来到 Stack Overflow!由于解释是该平台上答案的重要组成部分,请解释您的代码以及它如何解决问题中的问题以及为什么它可能比其他答案更好。我们的指南How to write a good answer 可能对您有所帮助。谢谢
    【解决方案11】:

    如果“大文件”意味着超出 4GB 限制,那么我下面的编写代码逻辑是合适的。需要注意的关键问题是与 SEEK 方法一起使用的 LONG 数据类型。因为 LONG 能够指向超过 2^32 个数据边界。 在此示例中,代码首先以 1GB 的块处理大文件,在处理完整个 1GB 的大块之后,处理剩余的 (https://crc32c.machinezoo.com/进行crc32c计算)

    private uint Crc32CAlgorithmBigCrc(string fileName)
    {
        uint hash = 0;
        byte[] buffer = null;
        FileInfo fileInfo = new FileInfo(fileName);
        long fileLength = fileInfo.Length;
        int blockSize = 1024000000;
        decimal div = fileLength / blockSize;
        int blocks = (int)Math.Floor(div);
        int restBytes = (int)(fileLength - (blocks * blockSize));
        long offsetFile = 0;
        uint interHash = 0;
        Crc32CAlgorithm Crc32CAlgorithm = new Crc32CAlgorithm();
        bool firstBlock = true;
        using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
        {
            buffer = new byte[blockSize];
            using (BinaryReader br = new BinaryReader(fs))
            {
                while (blocks > 0)
                {
                    blocks -= 1;
                    fs.Seek(offsetFile, SeekOrigin.Begin);
                    buffer = br.ReadBytes(blockSize);
                    if (firstBlock)
                    {
                        firstBlock = false;
                        interHash = Crc32CAlgorithm.Compute(buffer);
                        hash = interHash;
                    }
                    else
                    {
                        hash = Crc32CAlgorithm.Append(interHash, buffer);
                    }
                    offsetFile += blockSize;
                }
                if (restBytes > 0)
                {
                    Array.Resize(ref buffer, restBytes);
                    fs.Seek(offsetFile, SeekOrigin.Begin);
                    buffer = br.ReadBytes(restBytes);
                    hash = Crc32CAlgorithm.Append(interHash, buffer);
                }
                buffer = null;
            }
        }
        //MessageBox.Show(hash.ToString());
        //MessageBox.Show(hash.ToString("X"));
        return hash;
    }
    

    【讨论】:

      【解决方案12】:

      概述:如果您的图像作为 action= 嵌入资源添加,则使用 GetExecutingAssembly 将 jpg 资源检索到流中,然后将流中的二进制数据读取到字节数组中

         public byte[] GetAImage()
          {
              byte[] bytes=null;
              var assembly = Assembly.GetExecutingAssembly();
              var resourceName = "MYWebApi.Images.X_my_image.jpg";
      
              using (Stream stream = assembly.GetManifestResourceStream(resourceName))
              {
                  bytes = new byte[stream.Length];
                  stream.Read(bytes, 0, (int)stream.Length);
              }
              return bytes;
      
          }
      

      【讨论】:

        猜你喜欢
        • 2013-11-06
        • 1970-01-01
        • 2021-06-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-28
        • 2010-09-15
        相关资源
        最近更新 更多