当我读取500MB FileStream时OutOfMemoryException

时间:2010-05-11 09:37:49

标签: c# bytearray download filestream out-of-memory

我正在使用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;
    }

3 个答案:

答案 0 :(得分:33)

您显示的代码将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在发送第一个字节之前尝试缓冲所有文件数据。当您知道发送的文件非常大时,请使用它。数据仍将正确发送到浏览器。

即使这不是完整的解决方案。您需要设置响应标头等。不过,我猜你已经意识到了这一点。

答案 1 :(得分:4)

每次重新分配时,您的缓冲区大小都会增加一倍,这意味着以前分配的块永远不会被使用(它们会有效泄漏)。当你达到500 MB时,你已经嚼掉了1 GB以上的开销。实际上,它可能是2 GB,因为如果你达到512 MB,你的下一个分配将是1 GB。在32位系统上,这会破坏您的过程。

由于这是您正在读取的普通文件,只需查询文件系统的大小并一次性预分配缓冲区。

答案 2 :(得分:0)

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