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

问题描述 投票:351回答:11

我有一个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;
}
c# .net bytearray binary-data
11个回答
733
投票

只需用以下内容替换整个内容:

return File.ReadAllBytes(fileName);

但是,如果您担心内存消耗,则不应该一次性将所有文件都读入内存。你应该以大块的方式做到这一点。


-3
投票

我建议尝试使用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;
    }
}

62
投票

我可能会争辩说这里的答案通常是“不要”。除非您一次绝对需要所有数据,否则请考虑使用基于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);
    }
}

30
投票

我会这样想:

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

24
投票

您的代码可以考虑到这一点(代替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端口的标准驱动器?


9
投票

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

关键是,您应该考虑应用程序的整个生命周期,然后才能以最快的方式将所有字节读入内存,或者您可能会为整体性能进行短期性能交易。


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的问题,因为BinaryReader是这类事情的意思。此外,将其放入using声明确保FileStreamBinaryReader关闭并处置。


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/en-us/library/system.io.bufferedstream.aspx


0
投票

用这个:

 bytesRead = responseStream.ReadAsync(buffer, 0, Length).Result;
© www.soinside.com 2019 - 2024. All rights reserved.