Der beste Weg, um eine große Datei in ein Byte-Array in C # einzulesen?

391

Ich habe einen Webserver, der große Binärdateien (mehrere Megabyte) in Byte-Arrays liest. Der Server kann mehrere Dateien gleichzeitig lesen (unterschiedliche Seitenanforderungen), daher suche ich nach der optimierten Methode, um dies zu tun, ohne die CPU zu stark zu belasten. Ist der folgende Code gut genug?

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;
}
Tony_Henrich
quelle
60
Ihr Beispiel kann mit abgekürzt werden byte[] buff = File.ReadAllBytes(fileName).
Jesse C. Slicer
3
Warum bedeutet ein Webservice eines Drittanbieters, dass sich die Datei vollständig im RAM befinden muss, bevor sie an den Webservice gesendet und nicht gestreamt wird? Der Webservice wird den Unterschied nicht kennen.
Brian
@Brian, Einige Clients wissen nicht, wie sie mit einem .NET-Stream umgehen sollen, wie z. B. Java. In diesem Fall können Sie lediglich die gesamte Datei im Byte-Array lesen.
Sjeffrey
4
@sjeffrey: Ich sagte, die Daten sollten gestreamt und nicht als .NET-Stream übergeben werden. Die Kunden werden den Unterschied so oder so nicht kennen.
Brian

Antworten:

776

Ersetzen Sie einfach das Ganze durch:

return File.ReadAllBytes(fileName);

Wenn Sie jedoch über den Speicherverbrauch besorgt sind, sollten Sie nicht die gesamte Datei auf einmal in den Speicher einlesen. Sie sollten das in Stücken tun.

Mehrdad Afshari
quelle
40
Diese Methode ist auf 2 ^ 32-Byte-Dateien (4,2 GB) beschränkt
Mahmoud Farahat
11
File.ReadAllBytes löst OutOfMemoryException mit großen Dateien aus (getestet mit 630 MB Datei und es ist fehlgeschlagen)
Sakito
6
@ juanjo.arana Ja, na ja ... natürlich wird es immer etwas geben, das nicht in die Erinnerung passt. In diesem Fall gibt es keine Antwort auf die Frage. Im Allgemeinen sollten Sie die Datei streamen und nicht vollständig im Speicher speichern. Vielleicht möchten Sie dies für eine Notlösung betrachten: msdn.microsoft.com/en-us/library/hh285054%28v=vs.110%29.aspx
Mehrdad
4
In .NET gibt es eine Begrenzung für die Arraygröße.
illegal) -Imigrant
3
@harag Nein, und das ist nicht das, was die Frage stellt.
Mehrdad Afshari
72

Ich könnte argumentieren, dass die Antwort hier im Allgemeinen "nicht" ist. Wenn Sie nicht unbedingt alle Daten auf einmal benötigen , sollten Sie eine StreamAPI (oder eine Variante von Reader / Iterator) verwenden. Dies ist besonders wichtig, wenn Sie mehrere parallele Vorgänge ausführen (wie in der Frage vorgeschlagen), um die Systemlast zu minimieren und den Durchsatz zu maximieren.

Wenn Sie beispielsweise Daten an einen Anrufer streamen:

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);
    }
}
Marc Gravell
quelle
3
Um Ihre Aussage zu ergänzen, empfehle ich sogar, asynchrone ASP.NET-Handler in Betracht zu ziehen, wenn Sie eine E / A-gebundene Operation wie das Streamen einer Datei auf den Client haben. Wenn Sie jedochbyte[] aus irgendeinem Grund die gesamte Datei lesen müssen , sollten Sie keine Streams oder andere Elemente verwenden und nur die vom System bereitgestellte API verwenden.
Mehrdad Afshari
@Mehrdad - vereinbart; aber der vollständige Kontext ist nicht klar. Ebenso hat MVC Aktionsergebnisse dafür.
Marc Gravell
Ja, ich brauche alle Daten auf einmal. Es geht an einen Webservice eines Drittanbieters.
Tony_Henrich
Was ist die vom System bereitgestellte API?
Tony_Henrich
1
@ Tony: Ich habe in meiner Antwort angegeben : File.ReadAllBytes.
Mehrdad Afshari
32

Ich würde das denken:

byte[] file = System.IO.File.ReadAllBytes(fileName);
Powerlord
quelle
3
Beachten Sie, dass dies beim Abrufen sehr großer Dateien zum Stillstand kommen kann.
Vapcguy
28

Ihr Code kann dies berücksichtigen (anstelle von 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;
} 

Beachten Sie die Integer.MaxValue - Dateigrößenbeschränkung durch die Read-Methode. Mit anderen Worten, Sie können nur einen 2-GB-Block gleichzeitig lesen.

Beachten Sie auch, dass das letzte Argument für den FileStream eine Puffergröße ist.

Ich würde auch über das Lesen vorschlagen Filestream und BufferedStream .

Wie immer ist ein einfaches Beispielprogramm zum Profilieren, das am schnellsten ist, am vorteilhaftesten.

Auch Ihre zugrunde liegende Hardware hat einen großen Einfluss auf die Leistung. Verwenden Sie serverbasierte Festplatten mit großen Caches und eine RAID-Karte mit integriertem Speichercache? Oder verwenden Sie ein Standardlaufwerk, das an den IDE-Port angeschlossen ist?


quelle
Warum sollte die Art der Hardware einen Unterschied machen? Wenn es sich also um eine IDE handelt, verwenden Sie eine .NET-Methode, und wenn es sich um RAID handelt, verwenden Sie eine andere?
Tony_Henrich
@Tony_Henrich - Es hat nichts damit zu tun, welche Aufrufe Sie von Ihrer Programmiersprache aus tätigen. Es gibt verschiedene Arten von Festplatten. Beispielsweise werden Seagate-Laufwerke als "AS" oder "NS" klassifiziert, wobei NS das serverbasierte große Cache-Laufwerk ist, wobei das "AS" -Laufwerk das Consumer-Home-Computer-basierte Laufwerk ist. Suchgeschwindigkeiten und interne Übertragungsraten wirken sich auch darauf aus, wie schnell Sie etwas von der Festplatte lesen können. RAID-Arrays können die Lese- / Schreibleistung durch Caching erheblich verbessern. Möglicherweise können Sie die Datei auf einmal lesen, aber die zugrunde liegende Hardware ist immer noch der entscheidende Faktor.
2
Dieser Code enthält einen kritischen Fehler. Lesen ist nur erforderlich, um mindestens 1 Byte zurückzugeben.
Mafu
Ich würde sicherstellen, dass der Long-to-Int-Cast mit dem überprüften Konstrukt wie folgt
verpackt wird
Ich würde nur var binaryReader = new BinaryReader(fs); fileData = binaryReader.ReadBytes((int)fs.Length);in dieser usingAussage tun . Aber das ist effektiv so, wie es das OP getan hat. Ich habe nur eine Codezeile ausgeschnitten, indem ich auf Casting umgestellt habe fs.Length, intanstatt den longWert der FileInfoLänge zu ermitteln und diesen zu konvertieren.
Vapcguy
9

Abhängig von der Häufigkeit der Vorgänge, der Größe der Dateien und der Anzahl der Dateien, die Sie betrachten, müssen andere Leistungsprobleme berücksichtigt werden. Eine Sache, an die Sie sich erinnern sollten, ist, dass jedes Ihrer Byte-Arrays dem Garbage Collector ausgeliefert wird. Wenn Sie keine dieser Daten zwischenspeichern, können Sie viel Müll verursachen und den größten Teil Ihrer Leistung an % Time in GC verlieren. Wenn die Chunks größer als 85 KB sind, werden Sie dem Large Object Heap (LOH) zugeordnet, für dessen Freigabe eine Sammlung aller Generationen erforderlich ist (dies ist sehr teuer und auf einem Server wird die gesamte Ausführung gestoppt, während sie ausgeführt wird ). Wenn Sie eine Menge Objekte auf dem LOH haben, kann dies zu einer LOH-Fragmentierung führen (das LOH wird niemals komprimiert), was zu einer schlechten Leistung und Ausnahmen aufgrund von Speichermangel führt. Sie können den Prozess recyceln, sobald Sie einen bestimmten Punkt erreicht haben, aber ich weiß nicht, ob dies eine bewährte Methode ist.

Der Punkt ist, dass Sie den gesamten Lebenszyklus Ihrer App berücksichtigen sollten, bevor Sie unbedingt alle Bytes auf die schnellstmögliche Weise in den Speicher einlesen. Andernfalls können Sie kurzfristige Leistung gegen Gesamtleistung eintauschen.

Joel
quelle
Quellcode C # darüber, für Verwaltung garbage collector, chunks, Leistung, Ereigniszähler , ...
PreguntonCojoneroCabrón
6

Ich würde sagen, es BinaryReaderist in Ordnung, kann aber dahingehend überarbeitet werden, anstatt all dieser Codezeilen, um die Länge des Puffers zu ermitteln:

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

Sollte besser sein als zu verwenden .ReadAllBytes(), da ich in den Kommentaren zur Top-Antwort gesehen habe, .ReadAllBytes()dass einer der Kommentatoren Probleme mit Dateien> 600 MB hatte, da a BinaryReaderfür so etwas gedacht ist. Durch das Einfügen in eine usingErklärung wird auch sichergestellt, dass die FileStreamund BinaryReadergeschlossen und entsorgt werden.

vapcguy
quelle
Verwenden Sie für C # "using (FileStream fs = File.OpenRead (fileName))" anstelle von "using (FileStream fs = new File.OpenRead (fileName))" wie oben angegeben. Gerade neues Schlüsselwort vor File.OpenRead () entfernt
Syed Mohamed
@Syed Der obige Code wurde für C # geschrieben, aber Sie haben Recht, dass er newdort nicht benötigt wurde. Entfernt.
Vapcguy
1

Wenn mit 'eine große Datei' jenseits der 4-GB-Grenze gemeint ist, ist meine folgende geschriebene Codelogik angemessen. Das wichtigste Problem ist der lange Datentyp, der mit der SEEK-Methode verwendet wird. Da ein LONG in der Lage ist, über 2 ^ 32 Datengrenzen hinaus zu zeigen. In diesem Beispiel verarbeitet der Code zuerst die große Datei in Blöcken von 1 GB. Nachdem die großen ganzen Blöcke von 1 GB verarbeitet wurden, werden die verbleibenden (<1 GB) Bytes verarbeitet. Ich verwende diesen Code bei der Berechnung des CRC von Dateien über die Größe von 4 GB hinaus. (Verwenden von https://crc32c.machinezoo.com/ für die crc32c-Berechnung in diesem Beispiel)

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;
}
Menno de Ruiter
quelle
0

Verwenden Sie die BufferedStream-Klasse in C #, um die Leistung zu verbessern. Ein Puffer ist ein Block von Bytes im Speicher, der zum Zwischenspeichern von Daten verwendet wird, wodurch die Anzahl der Aufrufe des Betriebssystems verringert wird. Puffer verbessern die Lese- und Schreibleistung.

Im Folgenden finden Sie ein Codebeispiel und eine zusätzliche Erklärung: http://msdn.microsoft.com/en-us/library/system.io.bufferedstream.aspx

Todd Moses
quelle
Was bringt es, BufferedStreamwenn Sie das Ganze auf einmal lesen?
Mehrdad Afshari
Er bat um die beste Leistung, die Datei nicht sofort zu lesen.
Todd Moses
9
Die Leistung ist im Rahmen einer Operation messbar. Eine zusätzliche Pufferung für einen Stream, den Sie nacheinander auf einmal in den Speicher lesen, profitiert wahrscheinlich nicht von einem zusätzlichen Puffer.
Mehrdad Afshari
0

benutze das:

 bytesRead = responseStream.ReadAsync(buffer, 0, Length).Result;
Disha Sharma
quelle
2
Willkommen bei Stack Overflow! Da Erklärungen ein wichtiger Bestandteil der Antworten auf dieser Plattform sind, erläutern Sie bitte Ihren Code und wie er das Problem in der Frage löst und warum er möglicherweise besser ist als andere Antworten. Unser Leitfaden Wie Sie eine gute Antwort schreiben, kann für Sie hilfreich sein. Vielen Dank
David
-4

Ich würde empfehlen, die Response.TransferFile()Methode dann a Response.Flush()und Response.End()für die Bereitstellung Ihrer großen Dateien zu versuchen .

Dave
quelle
-7

Wenn Sie mit Dateien über 2 GB arbeiten, werden Sie feststellen, dass die oben genannten Methoden fehlschlagen.

Es ist viel einfacher, den Stream einfach an MD5 zu übergeben und zuzulassen, dass Ihre Datei für Sie aufgeteilt wird:

private byte[] computeFileHash(string filename)
{
    MD5 md5 = MD5.Create();
    using (FileStream fs = new FileStream(filename, FileMode.Open))
    {
        byte[] hash = md5.ComputeHash(fs);
        return hash;
    }
}
Elaverick
quelle
11
Ich sehe nicht, wie der Code für die Frage relevant ist (oder was Sie im geschriebenen Text vorschlagen)
Vojtech B