¿La mejor manera de leer un archivo grande en una matriz de bytes en C#?


Tengo un servidor web que leerá archivos binarios grandes (varios megabytes) en matrices de bytes. El servidor podría estar leyendo varios archivos al mismo tiempo (diferentes solicitudes de página), por lo que estoy buscando la forma más optimizada para hacer esto sin sobrecargar demasiado la CPU. ¿Es el código lo suficientemente bueno?

public byte[] FileToByteArray(string fileName)
{
    byte[] buff = null;
    FileStream fs = new FileStream(fileName, 
                                   FileMode.Open, 
                                   FileAccess.Read);
    BinaryReader br = new BinaryReader(fs);
    long numBytes = new FileInfo(fileName).Length;
    buff = br.ReadBytes((int) numBytes);
    return buff;
}
Author: Peter Mortensen, 2010-01-09

9 answers

Simplemente reemplace todo con:

return File.ReadAllBytes(fileName);

Sin embargo, si le preocupa el consumo de memoria, debe no leer todo el archivo en la memoria de una sola vez. Deberías hacerlo en trozos.

 649
Author: Mehrdad Afshari,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-08 21:36:23

Podría argumentar que la respuesta aquí generalmente es "no". A menos que necesite absolutamente todos los datos a la vez, considere usar una API basada en Stream (o alguna variante de reader / iterator). Eso es especialmente importante cuando tiene múltiples operaciones paralelas (como sugiere la pregunta) para minimizar la carga del sistema y maximizar el rendimiento.

Por ejemplo, si está transmitiendo datos a una persona que llama:

Stream dest = ...
using(Stream source = File.OpenRead(path)) {
    byte[] buffer = new byte[2048];
    int bytesRead;
    while((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0) {
        dest.Write(buffer, 0, bytesRead);
    }
}
 56
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-08 21:44:33

Yo pensaría esto:

byte[] file = System.IO.File.ReadAllBytes(fileName);
 29
Author: Powerlord,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-08 21:28:44

Su código se puede factorizar para esto (en lugar de File.ReadAllBytes):

public byte[] ReadAllBytes(string fileName)
{
    byte[] buffer = null;
    using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
    {
        buffer = new byte[fs.Length];
        fs.Read(buffer, 0, (int)fs.Length);
    }
    return buffer;
} 

Tenga en cuenta el entero.MaxValue-limitación de tamaño de archivo colocada por el método Read. En otras palabras, solo puede leer un fragmento de 2 GB a la vez.

También tenga en cuenta que el último argumento para el flujo de archivos es un tamaño de búfer.

También sugiero leer sobre FileStream y BufferedStream.

Como siempre, un simple programa de muestra para perfilar el que es más rápido será el más beneficioso.

También su hardware subyacente tendrá un gran efecto en el rendimiento. ¿Está utilizando unidades de disco duro basadas en servidor con cachés grandes y una tarjeta RAID con caché de memoria integrada? ¿O está utilizando una unidad estándar conectada al puerto IDE?

 22
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-08 21:51:17

Dependiendo de la frecuencia de las operaciones, el tamaño de los archivos y el número de archivos que esté viendo, hay otros problemas de rendimiento a tener en cuenta. Una cosa a recordar, es que cada una de sus matrices de bytes se liberará a merced del recolector de basura. Si no está almacenando en caché ninguno de esos datos, podría terminar creando mucha basura y perder la mayor parte de su rendimiento al % de tiempo en GC. Si los trozos son más grandes que 85K, estarás asignando al Gran Montón de Objetos (LOH) que requerirá una colección de todas las generaciones para liberar (esto es muy caro, y en un servidor detendrá toda la ejecución mientras está en marcha). Además, si tiene un montón de objetos en el LOH, puede terminar con fragmentación del LOH (el LOH nunca está compactado), lo que conduce a un rendimiento deficiente y excepciones de memoria fuera. Puedes reciclar el proceso una vez que llegues a cierto punto, pero no se si esa es una mejor práctica.

El punto es, usted debe considerar el ciclo de vida completo de su aplicación antes de leer necesariamente todos los bytes en la memoria de la manera más rápida posible o podría estar operando el rendimiento a corto plazo por el rendimiento general.

 9
Author: Joel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-08 22:25:19

Yo diría que BinaryReader está bien, pero se puede refactorizar a esto, en lugar de todas esas líneas de código para obtener la longitud del búfer:

public byte[] FileToByteArray(string fileName)
{
    byte[] fileData = null;

    using (FileStream fs = File.OpenRead(fileName)) 
    { 
        using (BinaryReader binaryReader = new BinaryReader(fs))
        {
            fileData = binaryReader.ReadBytes((int)fs.Length); 
        }
    }
    return fileData;
}

Debería ser mejor que usar .ReadAllBytes(), ya que vi en los comentarios en la respuesta superior que incluye .ReadAllBytes() que uno de los comentaristas tenía problemas con archivos > 600 MB, ya que un BinaryReader está destinado para este tipo de cosas. Además, colocarlo en una instrucción using asegura que FileStream y BinaryReader estén cerrados y dispuestos.

 4
Author: vapcguy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-09 17:38:56

Utilice la clase BufferedStream en C# para mejorar el rendimiento. Un búfer es un bloque de bytes en la memoria utilizado para almacenar datos en caché, reduciendo así el número de llamadas al sistema operativo. Los búferes mejoran el rendimiento de lectura y escritura.

Vea a continuación un ejemplo de código y una explicación adicional: http://msdn.microsoft.com/en-us/library/system.io.bufferedstream.aspx

 0
Author: Todd Moses,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-08 21:37:45

Recomendaría probar el método Response.TransferFile() luego un Response.Flush() y Response.End() para servir sus archivos grandes.

 -3
Author: Dave,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-08 17:01:49

Si está tratando con archivos de más de 2 GB, encontrará que los métodos anteriores fallan.

Es mucho más fácil simplemente entregar la transmisión a MD5 y permitir que eso corte su archivo por usted:

private byte[] computeFileHash(string filename)
{
    MD5 md5 = MD5.Create();
    using (FileStream fs = new FileStream(filename, FileMode.Open))
    {
        byte[] hash = md5.ComputeHash(fs);
        return hash;
    }
}
 -7
Author: elaverick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-26 19:10:28