14

I'm using Filestream for read big file (> 500 MB) and I get the OutOfMemoryException.

Any solutions about it.

My Code is:

 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;
    }
Charles
  • 11,269
  • 13
  • 67
  • 105
Alhambra Eidos
  • 1,515
  • 4
  • 21
  • 31

3 Answers3

42

The code you show, reads all content of the 500mb file into a contiguous region in memory. It's not surprising that you get an out-of-memory condition.

The solution is, "don't do that."

What are you really trying to do?


If you want to read a file completely, it's much simpler than the ReadFully method you use. Try this:

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
} 

But... using this code won't solve your problem. It might work for a 500mb file. It won't work for a 750mb file, or a 1gb file. At some point you will reach the limit of memory on your system and you will have the same out-of-memory error you started with.

The problem is that you are trying to hold the entire contents of the file in memory at one time. This is usually unnecessary, and is doomed to failure as the files grow in size. It's no problem when the filesize is 16k. At 500mb, it's the wrong approach.

This is why I have asked several times, what are you really trying to do ?


Sounds like you want to send the contents of a file out to an ASPNET response stream. This is the question. Not "how to read a 500mb file into memory?" But "how to send a large file to the ASPNET Response stream?"

For this, once again, it's fairly simple.

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

What it does is iteratively read a chunk from the file, and write that chunk to the Response stream, until there is nothing more to read in the file. This is what is meant by "streaming IO". The data passes through your logic, but is never held all in one place, just as a stream of water passes through a sluice. In this example, never is there more than 1k of file data in memory at one time (well, not held by your application code, anyway. There are other IO buffers lower in the stack.)

This is a common pattern in streamed IO. Learn it, use it.

The one trick when pumping data out to ASPNET's Response.OutputStream is to set BufferOutput = false. By default, ASPNET tries to buffer its output. In this case (500mb file), buffering is a bad idea. Setting the BufferOutput property to false will prevent ASPNET from attempting to buffer all the file data before sending the first byte. Use that when you know the file you're sending is very large. The data will still get sent to the browser correctly.

And even this isn't the complete solution. You'll need to set the response headers and so on. I guess you're aware of that, though.

Cheeso
  • 189,189
  • 101
  • 473
  • 713
  • In only want read a big file in byte[] to send in a asp.net page. ReadFully function is code of yoda.arachsys.com. thanks !!! http://www.yoda.arachsys.com/csharp/readbinary.html – Alhambra Eidos May 11 '10 at 13:04
  • 1
    why do you want the entire contents of this large file in memory at once? What are you *really* trying to do? – Cheeso May 11 '10 at 14:03
  • I only want read a big file in byte[] to send it to asp.net page like Response. ReadFully function is code of yoda.arachsys.com. thanks !!! yoda.arachsys.com/csharp/readbinary.html – Alhambra Eidos May 12 '10 at 06:29
  • 1
    You want to readd 500mb into memory, and it's failing. That means you don't have enough memory. Also, FileStream supports a Length property. You don't need to go through all the pushups of ReadFully() to read an entire file. Just allocate a buffer of size Length, and read once. An approach such as taken in ReadFully() is necessary only when you cannot know the length of the stream before reading it. – Cheeso May 12 '10 at 12:46
  • And how can I send the fat big file to Response Page ASP.NET ?? how can I read big file in C# ?? Thanks, please, any sample code ?? – Alhambra Eidos May 12 '10 at 13:39
  • Thanks mister, I'll try it. I want send the contents of a file out to an ASPNET response stream. My question, "how to send a large file to the ASPNET Response stream?" yes. Now, if I have WebServer asp.net, and services WCF. A page asp.net calls ServiceWCF for get byte[] of big bif file. There is another question... :-) – Alhambra Eidos May 20 '10 at 07:09
  • I agree this isn't what *he* wants to do. But it is what *I* want to do. :\ It's been 3 years, and I'd rather not error out on a 500MB file, even if it is unusually large, but I also don't want to change my approach when the large majority of files (really all of them) should be under 20MB. I'll find a solution, just not in this thread. I suspect it may be because I'm set on x86 right now, but I still shouldn't be anywhere near the 2GB limit for applications. – Serinus Jan 27 '14 at 22:08
  • 4
    @Serinus - You really DON'T want to do that. You don't need to "error out" on a 500mb file. Just stream it, as I said. The solution you seek is right here. You seem to not LIKE the solution for some reason, but it is the solution, I assure you. – Cheeso Jan 30 '14 at 20:24
  • Very useful solution for the issue I was having, while doing the exact same mistake; trying to send a large file through at a time. Now clients can even see the download progress as the file is pushed through! +1 – Lukas Dec 08 '17 at 21:58
  • I have similar problem. Where My WCF service calls another service(Windows Service) to fetch a pdf file around 15-30MB but returning that gives me out of memory issue. I tried your first suggestion in answer but it works for 12MB file and when I try with 20MB it's not working... any suggestions why 20 MB file is failing here I am sure that it's throwing exception when I read in buffer. Is there any config settings ? Please help – Renascent May 20 '22 at 10:09
4

You are doubling your buffer size at each reallocation, which means previously allocated blocks can never be used (they effectively leak). By the time you get to 500 MB, you've chewed up 1 GB plus overheads. In fact, it might be 2 GB, since, if you hit the 512 MB, your next allocation will be 1 GB. On a 32-bit system, this bankrupts your process.

Since it's a normal file you are reading in, just query the filesystem for its size and preallocate the buffer in one go.

Marcelo Cantos
  • 181,030
  • 38
  • 327
  • 365
  • Please, which is the best code, I using this: http://www.yoda.arachsys.com/csharp/readbinary.html Thanks mister – Alhambra Eidos May 11 '10 at 13:04
  • 1
    +1: Yes, allocating the buffer size you need is a good idea... actually, I'm surprised that .NET doesn't have a method to read an entire file into a byte array or some other similar structure. – Powerlord May 12 '10 at 14:30
  • 2
    It does. File.ReadAllBytes http://msdn.microsoft.com/en-us/library/system.io.file.readallbytes.aspx But that's not what this poster should be doing. Reading all the bytes of a 500mb file into memory is *usually a bad idea*, and in this case, ... it's a very bad idea. The poster clearly has in mind a primary, yet unstated goal that is not "read all the bytes of a file into memory." He *thinks* he needs to read all the bytes, but it's not true. – Cheeso May 20 '10 at 11:50
  • Just to add, I'm having the same issue with my Excel Addin VSTO. My C# code is attempting to ReadAllBytes to load in the raw data in a 60Mb Excel file, and even that is throwing an OutOfMemory exception. – Mike Gledhill Feb 25 '16 at 16:22
  • @MikeGledhill You generally shouldn't have trouble reading 60 MB into memory. Are you on a memory-constrained system with virtual memory turned off? – Marcelo Cantos Feb 26 '16 at 09:46
  • @Marcelo: Nope - this is on a laptop running Windows 7, Excel 2013, and 16Gb of memory. – Mike Gledhill Feb 26 '16 at 10:28
  • @MikeGledhill Something is horribly wrong. I can't see why you would get an OOM in that scenario. I wonder if maybe VSTO imposes restrictions on what addins can do. I suggest raising this is as a new SO question if you haven't already. – Marcelo Cantos Feb 26 '16 at 10:49
  • @Marcelo: I found a workaround. I was reading in all the bytes of the Excel file, to save it into a database record using LINQ. The workaround was to do a file-copy, copying the file onto a folder on the database server then get a Stored Procedure to bulk-insert the file into a db record. It's MUCH faster, and no need to read in all of the bytes on the user's machine. But it's odd that I needed to do this. – Mike Gledhill Feb 26 '16 at 19:32
0

Asp.Net Core Middleware

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;
    }
SUNIL DHAPPADHULE
  • 2,755
  • 17
  • 32