2010-05-11 85 views
9

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

有关它的任何解决方案。

我的代码是:

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

回答

4

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

由于这是一个正常的文件,你正在阅读,只是查询文件系统的大小和一次性预先分配缓冲区。

+0

请,这是最好的代码,我用这个:http://www.yoda.arachsys.com/csharp/readbinary.html 感谢老总 – 2010-05-11 13:04:29

+1

+1:是的,分配你需要的缓冲区大小是好主意......实际上,我很惊讶.NET没有将整个文件读入字节数组或其他类似结构的方法。 – Powerlord 2010-05-12 14:30:54

+2

它的确如此。 File.ReadAllBytes http://msdn.microsoft.com/en-us/library/system.io.file.readallbytes.aspx但这不是这张海报应该做的。将500MB文件的所有字节读入内存通常是个坏主意,在这种情况下,这是一个非常糟糕的主意。这个海报显然有一个主要的但没有说明的目标,它不是“将文件的所有字节读入内存”。他*认为*他需要读取所有字节,但事实并非如此。 – Cheeso 2010-05-20 11:50:37

30

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

即使这不是完整的解决方案。您需要设置响应标题等。不过,我想你知道这一点。

+0

只想在byte []中读取一个大文件发送到一个asp.net页面。 ReadFully函数是yoda.arachsys.com的代码。谢谢 !!! http://www.yoda.arachsys.com/csharp/readbinary.html – 2010-05-11 13:04:07

+1

你为什么要一次将这个大文件的全部内容存储在内存中?你真的*试图做什么? – Cheeso 2010-05-11 14:03:05

+0

我只想在byte []中读取一个大文件,将它发送到类似Response的asp.net页面。 ReadFully函数是yoda.arachsys.com的代码。谢谢 !!! yoda.arachsys.com/csharp/readbinary.html – 2010-05-12 06:29:22