Il modo migliore per leggere un file di grandi dimensioni in un array di byte in C #?


391

Ho un web server che leggerà grandi file binari (diversi megabyte) in array di byte. Il server potrebbe leggere più file contemporaneamente (richieste di pagine diverse), quindi sto cercando il modo più ottimizzato per farlo senza tassare troppo la CPU. Il codice qui sotto è abbastanza buono?

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

60
Il tuo esempio può essere abbreviato in byte[] buff = File.ReadAllBytes(fileName).
Jesse C. Slicer,

3
Perché essendo un servizio Web di terze parti implica che il file deve essere completamente nella RAM prima di essere inviato al servizio Web, anziché in streaming? Il servizio web non conoscerà la differenza.
Brian

@Brian, Alcuni client non sanno come gestire un flusso .NET, come ad esempio Java. In questo caso, tutto ciò che può essere fatto è leggere l'intero file nell'array di byte.
Jeffrey,

4
@sjeffrey: ho detto che i dati dovrebbero essere trasmessi in streaming, non passati come flusso .NET. I clienti non conosceranno la differenza in entrambi i casi.
Brian

Risposte:


776

Sostituisci semplicemente il tutto con:

return File.ReadAllBytes(fileName);

Tuttavia, se siete preoccupati per il consumo di memoria, si dovrebbe non leggere l'intero file in memoria tutto in una volta a tutti. Dovresti farlo a pezzi.


40
questo metodo è limitato a file da 2 ^ 32 byte (4,2 GB)
Mahmoud Farahat

11
File.ReadAllBytes genera OutOfMemoryException con file di grandi dimensioni (testato con file da 630 MB e non riuscito)
sakito

6
@ juanjo.arana Sì, beh ... ovviamente ci sarà sempre qualcosa che non si adatta alla memoria, nel qual caso, non c'è risposta alla domanda. In genere, è necessario eseguire lo streaming del file e non archiviarlo completamente in memoria. Potresti voler esaminare questo per una misura di
stopgap

4
Esiste un limite per la dimensione dell'array in .NET, ma in .NET 4.5 è possibile attivare il supporto per array di grandi dimensioni (> 2 GB) utilizzando l'opzione di configurazione speciale, vedere msdn.microsoft.com/en-us/library/hh285054.aspx
illegale -immigrante

3
@harag No, e non è questa la domanda.
Mehrdad Afshari,

72

Potrei sostenere che la risposta qui in genere è "non". A meno che tu non abbia assolutamente bisogno di tutti i dati contemporaneamente, Streamprendi in considerazione l'utilizzo di un'API basata su (o una variante del lettore / iteratore). Ciò è particolarmente importante quando si hanno più operazioni parallele (come suggerito dalla domanda) per ridurre al minimo il carico del sistema e massimizzare la produttività.

Ad esempio, se si esegue lo streaming di dati a un chiamante:

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

3
Per aggiungere alla tua affermazione, suggerisco anche di considerare i gestori ASP.NET asincroni se hai un'operazione di I / O associata come lo streaming di un file sul client. Tuttavia, se si deve leggere l'intero file in una byte[], per qualche motivo, mi permetto di suggerire evitano il ricorso ad corsi d'acqua o qualsiasi altra cosa e basta usare il sistema previsto API.
Mehrdad Afshari,

@Mehrdad: d'accordo; ma l'intero contesto non è chiaro. Allo stesso modo MVC ha risultati d'azione per questo.
Marc Gravell

Sì, ho bisogno di tutti i dati contemporaneamente. Andrà a un servizio web di terze parti.
Tony_Henrich,

Cos'è l'API fornita dal sistema?
Tony_Henrich,

1
@Tony: ho affermato nella mia risposta: File.ReadAllBytes.
Mehrdad Afshari,

32

Penserei questo:

byte[] file = System.IO.File.ReadAllBytes(fileName);

3
Si noti che questo può bloccarsi quando si ottengono file di grandi dimensioni.
vapcguy,

28

Il tuo codice può essere preso in considerazione per questo (al posto di 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;
} 

Nota Integer.MaxValue: limitazione della dimensione del file inserita dal metodo Read. In altre parole, puoi leggere solo un pezzo da 2 GB alla volta.

Si noti inoltre che l'ultimo argomento di FileStream è una dimensione del buffer.

Vorrei anche suggerire di leggere FileStream e BufferedStream .

Come sempre un semplice programma di esempio per profilare che è il più veloce sarà più vantaggioso.

Anche l'hardware sottostante avrà un grande effetto sulle prestazioni. Stai utilizzando unità disco rigido basate su server con cache di grandi dimensioni e una scheda RAID con cache di memoria integrata? Oppure stai utilizzando un'unità standard collegata alla porta IDE?


Perché il tipo di hardware potrebbe fare la differenza? Quindi se è IDE usi qualche metodo .NET e se è RAID ne usi un altro?
Tony_Henrich,

@Tony_Henrich - Non ha nulla a che fare con le chiamate che fai dal tuo linguaggio di programmazione. Esistono diversi tipi di unità disco fisso. Ad esempio, le unità Seagate sono classificate come "AS" o "NS" con NS come unità cache di server basata su server di grandi dimensioni, in quanto l'unità "AS" è l'unità consumer - computer di base. Le velocità di ricerca e le velocità di trasferimento interne influiscono anche sulla velocità con cui puoi leggere qualcosa dal disco. Gli array RAID possono migliorare notevolmente le prestazioni di lettura / scrittura attraverso la memorizzazione nella cache. Quindi potresti essere in grado di leggere il file tutto in una volta, ma l'hardware sottostante è ancora il fattore decisivo.

2
Questo codice contiene un bug critico. La lettura è necessaria solo per restituire almeno 1 byte.
mafu

Mi assicurerei di avvolgere il cast lungo a int con il costrutto controllato in questo modo: controllato ((int) fs.Length)
tzup

Vorrei solo fare var binaryReader = new BinaryReader(fs); fileData = binaryReader.ReadBytes((int)fs.Length);in quella usingdichiarazione. Ma questo è efficace come quello che il PO ha fatto, appena ho tagliato una riga di codice per colata fs.Lengthdi intinvece di ottenere il longvalore della FileInfolunghezza e la conversione di questo.
vapcguy,

9

A seconda della frequenza delle operazioni, della dimensione dei file e del numero di file che stai guardando, ci sono altri problemi di prestazioni da prendere in considerazione. Una cosa da ricordare è che ciascuno dei tuoi array di byte verrà rilasciato in balia del Garbage Collector. Se non stai memorizzando nella cache nessuno di questi dati, potresti finire per creare molta spazzatura e perdere la maggior parte delle tue prestazioni a % Time in GC. Se i blocchi sono più grandi di 85 KB, ti assegnerai al Large Object Heap (LOH) che richiederà una raccolta di tutte le generazioni per essere liberata (è molto costoso e su un server interromperà l'esecuzione mentre è in corso ). Inoltre, se hai un sacco di oggetti sul LOH, puoi finire con la frammentazione del LOH (il LOH non è mai compattato) che porta a scarse prestazioni e eccezioni di memoria insufficiente. Puoi riciclare il processo una volta raggiunto un certo punto, ma non so se sia una buona pratica.

Il punto è che dovresti considerare l'intero ciclo di vita della tua app prima di leggere semplicemente tutti i byte in memoria nel modo più veloce possibile o potresti scambiare prestazioni a breve termine per prestazioni complessive.


codice sorgente C # su di esso, per gestire garbage collector, chunks, prestazioni, contatori di eventi , ...
PreguntonCojoneroCabrón

6

Direi che BinaryReaderva bene, ma può essere refactored a questo, invece di tutte quelle righe di codice per ottenere la lunghezza del buffer:

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

Dovrebbe essere meglio dell'uso .ReadAllBytes(), poiché ho visto nei commenti sulla risposta principale che include .ReadAllBytes()che uno dei commentatori ha avuto problemi con file> 600 MB, dal momento che uno BinaryReaderè pensato per questo genere di cose. Inoltre, inserendolo in una usingdichiarazione si assicura FileStreamche BinaryReadersiano chiusi e smaltiti.


Per C #, è necessario utilizzare "using (FileStream fs = File.OpenRead (fileName))" invece di "using (FileStream fs = new File.OpenRead (fileName))" come indicato sopra. Ho appena rimosso una nuova parola chiave prima di File.OpenRead ()
Syed Mohamed il

@Syed Il codice sopra era stato scritto per C #, ma hai ragione che newnon era necessario lì. Rimosso.
Vapcguy,

1

Nel caso in cui "un file di grandi dimensioni" significhi oltre il limite di 4 GB, la mia logica di codice scritta seguente è appropriata. Il problema chiave da notare è il tipo di dati LONG utilizzato con il metodo SEEK. Come un LONG è in grado di puntare oltre 2 ^ 32 limiti di dati. In questo esempio, il codice sta elaborando prima l'elaborazione del file di grandi dimensioni in blocchi di 1 GB, dopo l'elaborazione dei blocchi di 1 GB interi di grandi dimensioni, vengono elaborati i byte rimasti (<1 GB). Uso questo codice per calcolare il CRC dei file oltre la dimensione di 4 GB. (utilizzando https://crc32c.machinezoo.com/ per il calcolo crc32c in questo esempio)

private uint Crc32CAlgorithmBigCrc(string fileName)
{
    uint hash = 0;
    byte[] buffer = null;
    FileInfo fileInfo = new FileInfo(fileName);
    long fileLength = fileInfo.Length;
    int blockSize = 1024000000;
    decimal div = fileLength / blockSize;
    int blocks = (int)Math.Floor(div);
    int restBytes = (int)(fileLength - (blocks * blockSize));
    long offsetFile = 0;
    uint interHash = 0;
    Crc32CAlgorithm Crc32CAlgorithm = new Crc32CAlgorithm();
    bool firstBlock = true;
    using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
    {
        buffer = new byte[blockSize];
        using (BinaryReader br = new BinaryReader(fs))
        {
            while (blocks > 0)
            {
                blocks -= 1;
                fs.Seek(offsetFile, SeekOrigin.Begin);
                buffer = br.ReadBytes(blockSize);
                if (firstBlock)
                {
                    firstBlock = false;
                    interHash = Crc32CAlgorithm.Compute(buffer);
                    hash = interHash;
                }
                else
                {
                    hash = Crc32CAlgorithm.Append(interHash, buffer);
                }
                offsetFile += blockSize;
            }
            if (restBytes > 0)
            {
                Array.Resize(ref buffer, restBytes);
                fs.Seek(offsetFile, SeekOrigin.Begin);
                buffer = br.ReadBytes(restBytes);
                hash = Crc32CAlgorithm.Append(interHash, buffer);
            }
            buffer = null;
        }
    }
    //MessageBox.Show(hash.ToString());
    //MessageBox.Show(hash.ToString("X"));
    return hash;
}

0

Utilizzare la classe BufferedStream in C # per migliorare le prestazioni. Un buffer è un blocco di byte in memoria utilizzato per memorizzare nella cache i dati, riducendo così il numero di chiamate al sistema operativo. I buffer migliorano le prestazioni di lettura e scrittura.

Vedere di seguito un esempio di codice e una spiegazione aggiuntiva: http://msdn.microsoft.com/en-us/library/system.io.bufferedstream.aspx


Qual è lo scopo di usare un BufferedStreamquando stai leggendo tutto in una volta?
Mehrdad Afshari,

Ha chiesto le migliori prestazioni per non leggere il file in una sola volta.
Todd Moses,

9
Le prestazioni sono misurabili nel contesto di un'operazione. Il buffering aggiuntivo per uno stream che stai leggendo in sequenza, tutto in una volta, nella memoria non è probabile che trarrà vantaggio da un buffer aggiuntivo.
Mehrdad Afshari,

0

Usa questo:

 bytesRead = responseStream.ReadAsync(buffer, 0, Length).Result;

2
Benvenuto in Stack Overflow! Poiché le spiegazioni sono una parte importante delle risposte su questa piattaforma, spiega il tuo codice e come risolve il problema nella domanda e perché potrebbe essere migliore di altre risposte. La nostra guida Come scrivere una buona risposta potrebbe esserti utile. Grazie
David

0

Panoramica: se l'immagine viene aggiunta come risorsa action = embedded, utilizzare GetExecutingAssembly per recuperare la risorsa jpg in un flusso, quindi leggere i dati binari nel flusso in un array di byte

   public byte[] GetAImage()
    {
        byte[] bytes=null;
        var assembly = Assembly.GetExecutingAssembly();
        var resourceName = "MYWebApi.Images.X_my_image.jpg";

        using (Stream stream = assembly.GetManifestResourceStream(resourceName))
        {
            bytes = new byte[stream.Length];
            stream.Read(bytes, 0, (int)stream.Length);
        }
        return bytes;

    }

-4

Vorrei consigliamo di provare il Response.TransferFile()metodo poi una Response.Flush()e Response.End()per servire i vostri file di grandi dimensioni.


-7

Se hai a che fare con file superiori a 2 GB, scoprirai che i metodi sopra indicati falliscono.

È molto più semplice distribuire lo stream su MD5 e consentire che blocchi il file per te:

private byte[] computeFileHash(string filename)
{
    MD5 md5 = MD5.Create();
    using (FileStream fs = new FileStream(filename, FileMode.Open))
    {
        byte[] hash = md5.ComputeHash(fs);
        return hash;
    }
}

11
Non vedo come il codice sia rilevante per la domanda (o cosa suggerisci nel testo scritto)
Vojtech B
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.