OutOfMemoryException cuando leo 500MB FileStream

Estoy usando Filestream para leer archivos grandes (> 500 MB) y obtengo OutOfMemoryException.

Cualquier solución al respecto.

Mi código es:

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

El código que muestra, lee todo el contenido del archivo de 500mb en una región contigua en la memoria. No es sorprendente que tengas una condición de falta de memoria.

La solución es “no hagas eso”.

¿Qué estás realmente tratando de hacer?


Si quieres leer un archivo por completo, es mucho más simple que el método ReadFully que usas. Prueba esto:

 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 } 

Pero … usar este código no resolverá tu problema. Podría funcionar para un archivo de 500mb. No funcionará para un archivo de 750 mb o un archivo de 1 gb. En algún momento alcanzará el límite de memoria en su sistema y tendrá el mismo error de falta de memoria con el que comenzó.

El problema es que está tratando de mantener todo el contenido del archivo en la memoria al mismo tiempo. Esto generalmente no es necesario y está condenado al fracaso a medida que los archivos crecen en tamaño. No hay problema cuando el tamaño del archivo es 16k. En 500mb, es el enfoque equivocado.

Es por eso que he preguntado varias veces, ¿qué estás realmente tratando de hacer ?


Parece que desea enviar el contenido de un archivo a una secuencia de respuesta de ASPNET. Esta es la pregunta. ¿No “cómo leer un archivo de 500mb en la memoria”? ¿Pero “cómo enviar un archivo grande a la stream de respuesta de ASPNET?”

Para esto, una vez más, es bastante 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); } } 

Lo que hace es leer iterativamente un fragmento del archivo y escribir ese fragmento en la secuencia de respuesta, hasta que no haya nada más que leer en el archivo. Esto es lo que significa “streaming IO”. Los datos pasan a través de su lógica, pero nunca se mantienen en un solo lugar, al igual que una stream de agua pasa a través de una compuerta. En este ejemplo, nunca hay más de 1k de datos de archivos en la memoria al mismo tiempo (bueno, no es lo que guarda el código de la aplicación, de todos modos. Hay otros almacenamientos intermedios de IO más abajo en la stack).

Este es un patrón común en IO transmitido. Aprende, úsala.

El único truco al transferir datos a Response.OutputStream de ASPNET es establecer BufferOutput = false . De forma predeterminada, ASPNET intenta almacenar en búfer su salida. En este caso (archivo de 500 mb), el almacenamiento en búfer es una mala idea. Establecer la propiedad BufferOutput en falso evitará que ASPNET intente almacenar en búfer todos los datos del archivo antes de enviar el primer byte. Úselo cuando sepa que el archivo que está enviando es muy grande. Los datos seguirán siendo enviados al navegador correctamente.

E incluso esta no es la solución completa. Tendrá que configurar los encabezados de respuesta, etc. Sin embargo, supongo que eres consciente de eso.

Está duplicando el tamaño de su búfer en cada reasignación, lo que significa que los bloques asignados previamente nunca se pueden usar (se filtran efectivamente). Cuando llega a 500 MB, ha masticado 1 GB más gastos generales. De hecho, podría ser de 2 GB, ya que, si alcanzas los 512 MB, tu próxima asignación será de 1 GB. En un sistema de 32 bits, esto arruina su proceso.

Dado que es un archivo normal en el que está leyendo, simplemente consulte el sistema de archivos para su tamaño y preasigne el búfer de una vez.