簡體   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