当我读取500MB FileStream时OutOfMemoryException

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