在C#中将大文件读入字节数组的最佳方法?


391

我有一个Web服务器,它将大的二进制文件(几兆字节)读入字节数组。服务器可能同时读取多个文件(不同的页面请求),因此我正在寻找最优化的方式来执行此操作,而又不会给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;
}

60
您的示例可以缩写为byte[] buff = File.ReadAllBytes(fileName)
Jesse C. Slicer 2010年

3
为什么它是第三方Web服务,意味着文件在发送到Web服务之前需要完全在RAM中而不是通过流传输?Web服务将不知道它们之间的区别。
Brian

@Brian,有些客户端不知道如何处理.NET流,例如Java。在这种情况下,所有可以做的就是读取字节数组中的整个文件。
sjeffrey 2012年

4
@sjeffrey:我说过应该将数据流化,而不是作为.NET流传递。客户不会知道两者之间的区别。
Brian

Answers:


776

只需将整个内容替换为:

return File.ReadAllBytes(fileName);

但是,如果您担心内存消耗,你应该不会一下子在所有读取整个文件到内存中。您应该分块进行。


40
此方法仅限于2 ^ 32字节文件(4.2 GB)
Mahmoud Farahat 2012年

11
File.ReadAllBytes抛出带有大文件的OutOfMemoryException(已测试630 MB文件,但失败了)
sakito 2013年

6
@ juanjo.arana是的,嗯……当然总会有不适合记忆的东西,在这种情况下,这个问题没有答案。通常,您应该流式传输文件,而不要将其完全存储在内存中。您可能需要考虑一下权宜之计:msdn.microsoft.com/en-us/library/hh285054%28v=vs.110%29.aspx
Mehrdad Afshari 2013年

4
.NET中的数组大小有限制,但是在.NET 4.5中,您可以使用特殊的配置选项打开对大型数组(> 2GB)的支持,请参阅msdn.microsoft.com/en-us/library/hh285054.aspx
非法-immigrant

3
@harag不,这不是问题要问的。
Mehrdad Afshari

72

我可能会争辩说,这里的答案通常是“不”。除非您一次绝对需要所有数据,否则请考虑使用Stream基于-的API(或Reader / Iterator的某些变体)。当您有多个并行操作(如问题所建议的)以最大程度地减少系统负载并最大化吞吐量时,这一点尤其重要。

例如,如果您正在将数据流传输给呼叫者:

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

3
要添加到语句中,如果您具有I / O绑定操作(例如将文件流式传输到客户端),我什至建议您考虑使用异步ASP.NET处理程序。但是,如果由于某种原因必须读取整个文件byte[],建议不要使用流或其他任何方法,而应使用系统提供的API。
Mehrdad Afshari 2010年

@Mehrdad-同意;但尚不清楚全部内容。同样,MVC对此具有操作结果。
马克

是的,我一次需要所有数据。它将转到第三方Web服务。
Tony_Henrich,2010年

系统提供的API是什么?
Tony_Henrich,2010年

1
@Tony:我在回答中说:File.ReadAllBytes
Mehrdad Afshari 2010年

32

我会这样认为:

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

3
请注意,当获取非常大的文件时,这可能会停止。
vapcguy

28

可以将您的代码分解为该代码(代替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,则使用另一种.NET方法?
Tony_Henrich,2010年

@Tony_Henrich-它与您从编程语言进行的调用无关。有不同类型的硬盘驱动器。例如,Seagate驱动器被分类为“ AS”或“ NS”,其中NS是基于服务器的大型缓存驱动器,其中“ AS”驱动器是消费者-基于家用计算机的驱动器。搜寻速度和内部传输速率也会影响您从磁盘读取内容的速度。RAID阵列可以通过缓存极大地提高读/写性能。因此,您也许可以一次读取所有文件,但是底层硬件仍然是决定因素。

2
此代码包含一个严重的错误。仅要求读才能返回至少1个字节。
mafu 2012年

我要确保将long到int类型的转换用如下选中的结构包装起来:checked((int)fs.Length)
tzup 2014年

我只是var binaryReader = new BinaryReader(fs); fileData = binaryReader.ReadBytes((int)fs.Length);在那using句话中做。但这实际上就像OP所做的一样,只是我通过强制转换fs.Lengthint来剪切一行代码,而不是获取长度的longFileInfo并进行转换。
vapcguy's

9

根据操作频率,文件大小以及要查看的文件数量,还需要考虑其他性能问题。要记住的一件事是,每个字节数组都将由垃圾收集器释放。如果您不缓存任何数据,最终可能会造成大量垃圾,并使大部分性能损失给GC时间百分比。如果块大于85K,则将分配给大对象堆(LOH),这将需要所有代的集合才能释放(这非常昂贵,并且在服务器上,它将继续停止所有执行) )。此外,如果LOH上有大量对象,则可能会导致LOH碎片化(永远不会压缩LOH),这会导致性能下降和内存不足异常。一旦达到特定点,您就可以回收该过程,但是我不知道这是否是最佳实践。

关键是,在必须以最快的方式将所有字节读入内存之前,您应该考虑应用程序的整个生命周期,否则您可能会为了整体性能而牺牲短期性能。


源代码C#这件事,对于管理garbage collectorchunks性能,事件计数器,...
PreguntonCojoneroCabrón

6

我会说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(),因为我在最高响应的注释中看到其中包括.ReadAllBytes()一个注释者的文件大小超过600 MB的问题,因为a BinaryReader就是用于这种情况。同样,将其放在using语句中可确保FileStreamBinaryReader被关闭和处置。


对于C#,需要使用“使用(FileStream fs = File.OpenRead(fileName))”,而不是如上所述的“使用(FileStream fs = new File.OpenRead(fileName))”。刚刚在File.OpenRead()之前删除了新关键字
Syed Mohamed

@Syed上面的代码是为C#编写的,但是您是对的,new这里并不需要。已移除。
vapcguy

1

如果“大文件”意味着超出4GB的限制,那么我下面的书面代码逻辑是合适的。需要注意的关键问题是SEEK方法使用的LONG数据类型。由于LONG可以指向2 ^ 32的数据边界。在此示例中,代码正在处理首先处理1GB块的大文件,在处理了整个1GB的大块之后,处理了剩余的(<1GB)字节。我使用此代码来计算超过4GB大小的文件的CRC。(在此示例中,使用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;
}

0

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

请参见以下代码示例和其他说明:http : //msdn.microsoft.com/zh-cn/library/system.io.bufferedstream.aspx


BufferedStream当您一次阅读整本书时,使用a有什么意义?
Mehrdad Afshari 2010年

他要求最好的性能是不要一次读取文件。
托德·摩西

9
在操作的上下文中可以衡量性能。为您正在依次顺序读取到的流的附加缓冲(一次)到内存不太可能受益于附加缓冲。
Mehrdad Afshari 2010年

0

用这个:

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

2
欢迎使用Stack Overflow!由于解释是此平台上答案的重要组成部分,请解释您的代码以及代码如何解决问题中的内容以及为什么它可能比其他答案更好。我们的指南“ 如何写一个好的答案”可能对您有所帮助。谢谢
David

-4

我建议尝试Response.TransferFile()然后使用方法Response.Flush()Response.End()服务您的大文件。


-7

如果您要处理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;
    }
}

11
我看不到代码与问题(或您在书面文本中的建议)之间的关系如何
Vojtech B
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.