当我读取 500MB FileStream 时出现 OutOfMemoryException

Posted

技术标签:

【中文标题】当我读取 500MB FileStream 时出现 OutOfMemoryException【英文标题】:OutOfMemoryException when I read 500MB FileStream 【发布时间】:2010-05-11 09:37:49 【问题描述】:

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

【问题讨论】:

【参考方案1】:

您显示的代码将 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);
   
 

它的作用是迭代地从文件中读取一个块,并将该块写入响应流,直到文件中没有更多内容可读取。这就是“流式 IO”的含义。数据通过您的逻辑,但永远不会全部保存在一个地方,就像水流穿过水闸一样。在此示例中,内存中的文件数据永远不会超过 1k(好吧,无论如何,您的应用程序代码不会保存。堆栈中还有其他 IO 缓冲区。)

这是流式 IO 中的常见模式。学习它,使用它。

将数据抽出到 ASPNET 的 Response.OutputStream 时的一个技巧是设置 BufferOutput = false。默认情况下,ASPNET 尝试缓冲其输出。在这种情况下(500mb 文件),缓冲是个坏主意。将BufferOutput 属性设置为false 将阻止ASPNET 在发送第一个字节之前尝试缓冲所有文件数据。当您知道要发送的文件非常大时,请使用它。数据仍将正确发送到浏览器。

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

【讨论】:

只想读取 byte[] 中的大文件以发送到 asp.net 页面。 ReadFully 函数是 yoda.arachsys.com 的代码。谢谢 !!! yoda.arachsys.com/csharp/readbinary.html 为什么要将这个大文件的全部内容一次放到内存中?你真正想做什么? 我只想读取 byte[] 中的一个大文件以将其发送到 Response 等 asp.net 页面。 ReadFully 函数是 yoda.arachsys.com 的代码。谢谢 !!! yoda.arachsys.com/csharp/readbinary.html 您想将 500mb 读入内存,但它失败了。这意味着你没有足够的内存。此外,FileStream 支持 Length 属性。您不需要通过 ReadFully() 的所有俯卧撑来读取整个文件。只需分配一个大小为 Length 的缓冲区,然后读取一次。仅当您在读取流之前无法知道流的长度时,才需要使用 ReadFully() 等方法。 @Serinus - 你真的不想那样做。您不需要在 500mb 文件上“出错”。正如我所说,只需流式传输即可。您寻求的解决方案就在这里。出于某种原因,您似乎不喜欢该解决方案,但我向您保证,它就是解决方案。【参考方案2】:

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

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

【讨论】:

拜托,这是最好的代码,我用这个:yoda.arachsys.com/csharp/readbinary.html谢谢先生 +1:是的,分配所需的缓冲区大小是个好主意……实际上,我很惊讶 .NET 没有将整个文件读入字节数组的方法或其他一些类似的结构。 确实如此。 File.ReadAllBytes msdn.microsoft.com/en-us/library/… 但这不是这张海报应该做的。将 500mb 文件的所有字节读入内存通常是个坏主意,在这种情况下,……这是个非常坏的主意。发帖人显然有一个主要但未说明的目标,即不是“将文件的所有字节读入内存”。他认为他需要读取所有字节,但事实并非如此。 只是补充一下,我的 Excel 插件 VSTO 也有同样的问题。我的 C# 代码正在尝试 ReadAllBytes 以加载 60Mb Excel 文件中的原始数据,即使这样也会引发 OutOfMemory 异常。 @MikeGledhill 将 60 MB 读入内存通常不会有问题。您是否在关闭虚拟内存的内存受限系统上?【参考方案3】:

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;
    

【讨论】:

以上是关于当我读取 500MB FileStream 时出现 OutOfMemoryException的主要内容,如果未能解决你的问题,请参考以下文章

超过 128MB 的纹理时出现 OpenGL“内存不足”错误

使用 ZipOutputStream 和 FileStream 压缩文件时出现异常

上传到 Azure 存储时出现内部 500 错误

上传文件时出现 HTTP 500 内部服务器错误 [重复]

进行预览时出现 Bigquery API 限制超出错误

替换 Azure Blob 存储中的文件时出现错误 500