簡體   English   中英

當我讀取500MB FileStream時OutOfMemoryException

[英]OutOfMemoryException when I read 500MB FileStream

我正在使用Filestream讀取大文件(> 500 MB),我得到了OutOfMemoryException。

任何解決方案。

我的代碼是:

 using (var fs3 = new FileStream(filePath2, FileMode.Open, FileAccess.Read))
                {
                    byte[] b2 = ReadFully(fs3, 1024);
                }


 public static byte[] ReadFully(Stream stream, int initialLength)
    {
        // If we've been passed an unhelpful initial length, just
        // use 32K.
        if (initialLength < 1)
        {
            initialLength = 32768;
        }

        byte[] buffer = new byte[initialLength];
        int read = 0;

        int chunk;
        while ((chunk = stream.Read(buffer, read, buffer.Length - read)) > 0)
        {
            read += chunk;

            // If we've reached the end of our buffer, check to see if there's
            // any more information
            if (read == buffer.Length)
            {
                int nextByte = stream.ReadByte();

                // End of stream? If so, we're done
                if (nextByte == -1)
                {
                    return buffer;
                }

                // Nope. Resize the buffer, put in the byte we've just
                // read, and continue
                byte[] newBuffer = new byte[buffer.Length * 2];
                Array.Copy(buffer, newBuffer, buffer.Length);
                newBuffer[read] = (byte)nextByte;
                buffer = newBuffer;
                read++;
            }
        }
        // Buffer is now too big. Shrink it.
        byte[] ret = new byte[read];
        Array.Copy(buffer, ret, read);
        return ret;
    }

您顯示的代碼將500mb文件的所有內容讀入內存中的連續區域。 你得到一個內存不足的情況並不奇怪。

解決方案是“不要那樣做”。

真的想做什么?


如果要完全讀取文件,它比您使用的ReadFully方法簡單得多。 試試這個:

using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) 
{ 
   byte[] buffer = new byte[fs.Length];
   int bytesRead = fs.Read(buffer, 0, buffer.Length);
   // buffer now contains the entire contents of the file
} 

但是......使用此代碼無法解決您的問題。 它可能適用於500mb文件。 它不適用於750mb文件或1gb文件。 在某些時候,您將達到系統內存的限制,並且您將遇到與開始時相同的內存不足錯誤。

問題是您試圖一次將文件的全部內容保存在內存中。 這通常是不必要的,並且隨着文件大小的增加注定要失敗。 文件大小為16k時沒問題。 在500mb,這是錯誤的方法。

這就是我多次問過為什么要做的原因


聽起來好像你想將文件的內容發送到ASPNET響應流。 這是個問題。 不是“如何將500mb文件讀入內存?” 但“如何將大文件發送到ASPNET響應流?”

為此,再一次,它相當簡單。

// emit the contents of a file into the ASPNET Response stream
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) 
{ 
   Response.BufferOutput= false;   // to prevent buffering
   byte[] buffer = new byte[1024];
   int bytesRead = 0;
   while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0) 
   {
       Response.OutputStream.Write(buffer, 0, bytesRead);
   }
} 

它的作用是迭代地從文件中讀取一個塊,並將該塊寫入Response流,直到文件中沒有其他內容可讀。 這就是“流式IO”的含義。 數據通過你的邏輯,但永遠不會全部集中在一個地方,就像水流通過水閘一樣。 在這個例子中,一次內存中永遠不會有超過1k的文件數據(好吧,不管你的應用程序代碼是不是這樣。堆棧中還有其他IO緩沖區。)

這是流式IO中的常見模式。 學習它,使用它。

將數據輸出到ASPNET的Response.OutputStream時的一個技巧是設置BufferOutput = false 默認情況下,ASPNET嘗試緩沖其輸出。 在這種情況下(500mb文件),緩沖是一個壞主意。 BufferOutput屬性設置為false將阻止ASPNET在發送第一個字節之前嘗試緩沖所有文件數據。 當您知道發送的文件非常大時,請使用它。 數據仍將正確發送到瀏覽器。

即使這不是完整的解決方案。 您需要設置響應標頭等。 不過,我猜你已經意識到了這一點。

您在每次重新分配時將緩沖區大小加倍,這意味着以前分配的塊永遠不會被使用(它們有效地泄漏)。 當你達到500 MB時,你已經嚼掉了1 GB以上的開銷。 實際上,它可能是2 GB,因為如果你達到512 MB,你的下一個分配將是1 GB。 在32位系統上,這會破壞您的進程。

由於它是您正在讀取的普通文件,因此只需查詢文件系統的大小並一次性預分配緩沖區。

Asp.Net核心中間件

public static async Task<string> GetRequestBody(HttpContext context)
    {
        string bodyText = string.Empty;
        try
        {
            var requestbody = context.Request.Body;
            context.Request.EnableRewind();
            int offset = 0, bytesread = 0;
            var buffer = new byte[5096];
            while ((bytesread = await context.Request.Body.ReadAsync(buffer, offset, buffer.Length - offset)) > 0)
            {
                offset += bytesread;
                if (offset == buffer.Length)
                {
                    int nextByte = context.Request.Body.ReadByte();
                    if (nextByte == -1)
                    {
                        break;
                    }
                    byte[] newBuffer = new byte[buffer.Length * 2];
                    Array.Copy(buffer, newBuffer, buffer.Length);//how to avoid copy 
                    newBuffer[offset] = (byte)nextByte;//how to avoid boxing 
                    buffer = newBuffer;
                    offset++;
                }
                if (offset > 4194304)
                {
                    //log.Warn("Middleware/GetRequestBody--> Request length exceeding limit");
                    break;
                }
            }
            bodyText = Encoding.UTF8.GetString(buffer);
        }
        catch (Exception ex)
        {
            //log.Debug(ex, "Middleware/GetRequestBody--> Request length exceeding limit");
        }
        context.Request.Body.Position = 0;
        return bodyText;
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM