繁体   English   中英

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

[英]Best way to read a large file into a byte array in C#?

我有一个 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;
}

只需将整个内容替换为:

return File.ReadAllBytes(fileName);

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

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

我会认为:

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

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

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

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

我会说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被关闭和处理。

如果“大文件”意味着超出 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;
}

概述:如果您的图像作为 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;

    }

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

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

使用这个:

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

我建议先尝试Response.TransferFile()方法,然后再尝试Response.Flush()Response.End()来处理大文件。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM