Was ist der schnellste Weg, eine Textdatei Zeile für Zeile zu lesen?

318

Ich möchte eine Textdatei Zeile für Zeile lesen. Ich wollte wissen, ob ich es im Rahmen von .NET C # so effizient wie möglich mache.

Das versuche ich bisher:

var filestream = new System.IO.FileStream(textFilePath,
                                          System.IO.FileMode.Open,
                                          System.IO.FileAccess.Read,
                                          System.IO.FileShare.ReadWrite);
var file = new System.IO.StreamReader(filestream, System.Text.Encoding.UTF8, true, 128);

while ((lineOfText = file.ReadLine()) != null)
{
    //Do something with the lineOfText
}
Loren C Fortner
quelle
7
Mit Fastestmeinen Sie aus Leistungs- oder Entwicklungsperspektive?
sll
1
Dadurch wird die Datei für die Dauer der Methode gesperrt. Sie können File.ReadAllLines in einem Array verwenden und dann das Array verarbeiten.
Kell
17
BTW, anbei filestream = new FileStreamin using()Anweisung zu vermeiden mögliche lästige Probleme mit gesperrter Datei Griff
SLL
Informationen zum Einschließen der FileStream-Anweisung using () finden Sie unter StackOverflow zur empfohlenen Methode: StackOverflow mithilfe der Anweisung filestream streamreader
deegee
Ich denke, ReadToEnd () ist schneller.
Dan Gifford

Antworten:

315

Um den schnellsten Weg zu finden, eine Datei Zeile für Zeile zu lesen, müssen Sie ein Benchmarking durchführen. Ich habe einige kleine Tests auf meinem Computer durchgeführt, aber Sie können nicht erwarten, dass meine Ergebnisse für Ihre Umgebung gelten.

Verwenden von StreamReader.ReadLine

Dies ist im Grunde Ihre Methode. Aus irgendeinem Grund setzen Sie die Puffergröße auf den kleinstmöglichen Wert (128). Wenn Sie dies erhöhen, wird die Leistung im Allgemeinen erhöht. Die Standardgröße ist 1.024 und andere gute Optionen sind 512 (die Sektorgröße in Windows) oder 4.096 (die Clustergröße in NTFS). Sie müssen einen Benchmark ausführen, um eine optimale Puffergröße zu ermitteln. Ein größerer Puffer ist - wenn nicht schneller - zumindest nicht langsamer als ein kleinerer Puffer.

const Int32 BufferSize = 128;
using (var fileStream = File.OpenRead(fileName))
  using (var streamReader = new StreamReader(fileStream, Encoding.UTF8, true, BufferSize)) {
    String line;
    while ((line = streamReader.ReadLine()) != null)
      // Process line
  }

Mit dem FileStreamKonstruktor können Sie FileOptions angeben . Wenn Sie beispielsweise eine große Datei nacheinander von Anfang bis Ende lesen, können Sie davon profitierenFileOptions.SequentialScan . Auch hier ist Benchmarking das Beste, was Sie tun können.

Verwenden von File.ReadLines

Dies ist Ihrer eigenen Lösung sehr ähnlich, außer dass sie StreamReadermit einer festen Puffergröße von 1.024 implementiert wird . Auf meinem Computer führt dies zu einer etwas besseren Leistung im Vergleich zu Ihrem Code mit der Puffergröße von 128. Sie können jedoch die gleiche Leistungssteigerung erzielen, indem Sie eine größere Puffergröße verwenden. Diese Methode wird mithilfe eines Iteratorblocks implementiert und belegt nicht für alle Zeilen Speicher.

var lines = File.ReadLines(fileName);
foreach (var line in lines)
  // Process line

Verwenden von File.ReadAllLines

Dies ist der vorherigen Methode sehr ähnlich, außer dass diese Methode eine Liste von Zeichenfolgen erstellt, die zum Erstellen des zurückgegebenen Zeilenarrays verwendet werden, sodass die Speicheranforderungen höher sind. Es wird jedoch zurückgegeben und Sie können String[]nicht IEnumerable<String>zufällig auf die Zeilen zugreifen.

var lines = File.ReadAllLines(fileName);
for (var i = 0; i < lines.Length; i += 1) {
  var line = lines[i];
  // Process line
}

Verwenden von String.Split

Diese Methode ist erheblich langsamer, zumindest bei großen Dateien (getestet an einer 511-KB-Datei), wahrscheinlich aufgrund der String.SplitImplementierung. Außerdem wird allen Zeilen ein Array zugewiesen, wodurch der im Vergleich zu Ihrer Lösung erforderliche Speicher erhöht wird.

using (var streamReader = File.OpenText(fileName)) {
  var lines = streamReader.ReadToEnd().Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
  foreach (var line in lines)
    // Process line
}

Mein Vorschlag ist zu verwenden, File.ReadLinesweil es sauber und effizient ist. Wenn Sie spezielle Freigabeoptionen benötigen (z. B. verwenden FileShare.ReadWrite), können Sie Ihren eigenen Code verwenden, aber Sie sollten die Puffergröße erhöhen.

Martin Liversage
quelle
1
Vielen Dank dafür - Ihre Aufnahme des Parameters Puffergröße in den StreamReader-Konstruktor war wirklich hilfreich. Ich streame von der S3-API von Amazon und die Verwendung einer passenden Puffergröße beschleunigt die Arbeit in Verbindung mit ReadLine () erheblich.
Richard K.
Ich verstehe nicht Theoretisch würde die überwiegende Mehrheit der Zeit, die zum Lesen der Datei aufgewendet wird, die Suchzeit auf der Festplatte und der Aufwand für die Verwaltung von Streams sein, wie Sie es mit File.ReadLines tun würden. File.ReadLines hingegen soll alles einer Datei auf einmal in den Speicher lesen. Wie könnte es schlechter in der Leistung sein?
h9uest
2
Ich kann nichts über die Geschwindigkeitsleistung sagen, aber eines ist sicher: Der Speicherverbrauch ist weitaus schlechter. Wenn Sie mit sehr großen Dateien (z. B. GB) umgehen müssen, ist dies sehr wichtig. Noch mehr, wenn es bedeutet, dass der Speicher ausgetauscht werden muss. Auf der Geschwindigkeitsseite können Sie hinzufügen, dass ReadAllLine ALLE Zeilen lesen muss, bevor das Ergebnis die Verarbeitung verzögert. In einigen Szenarien ist der EINDRUCK der Geschwindigkeit wichtiger als die Rohgeschwindigkeit.
bkqc
Wenn Sie den Stream als Byte-Arrays lesen, wird die Datei von 20% bis 80% schneller gelesen (aus den von mir durchgeführten Tests). Sie müssen das Byte-Array abrufen und in einen String konvertieren. So habe ich es gemacht: Verwenden Sie zum Lesen stream.Read (). Sie können eine Schleife erstellen , um sie in Blöcken lesen zu können. Nachdem Sie den gesamten Inhalt an ein Byte-Array angehängt haben (verwenden Sie System.Buffer.BlockCopy ), müssen Sie die Bytes in eine Zeichenfolge konvertieren: Encoding.Default.GetString (byteContent, 0, byteContent.Length - 1) .Split (neue Zeichenfolge [ ] {"\ r \ n", "\ r", "\ n"}, StringSplitOptions.None);
Kim Lage
200

Wenn Sie .NET 4 verwenden, verwenden Sie einfach, File.ReadLineswas alles für Sie erledigt. Ich vermute, es ist fast dasselbe wie deins, außer dass es auch FileOptions.SequentialScaneinen größeren Puffer verwenden kann (128 scheint sehr klein zu sein).

Jon Skeet
quelle
Ein weiterer Vorteil von ReadLines()ist, dass es faul ist und daher gut mit LINQ funktioniert.
stt106
35

Dies File.ReadAllLines()ist eine der einfachsten Möglichkeiten zum Lesen einer Datei, aber auch eine der langsamsten.

Wenn Sie nur Zeilen in einer Datei lesen möchten, ohne viel zu tun, ist nach diesen Benchmarks der schnellste Weg, eine Datei zu lesen, die uralte Methode von:

using (StreamReader sr = File.OpenText(fileName))
{
        string s = String.Empty;
        while ((s = sr.ReadLine()) != null)
        {
               //do minimal amount of work here
        }
}

Wenn Sie jedoch mit jeder Zeile viel tun müssen, kommt dieser Artikel zu dem Schluss, dass der beste Weg der folgende ist (und es ist schneller, eine Zeichenfolge [] vorab zuzuweisen, wenn Sie wissen, wie viele Zeilen Sie lesen werden):

AllLines = new string[MAX]; //only allocate memory here

using (StreamReader sr = File.OpenText(fileName))
{
        int x = 0;
        while (!sr.EndOfStream)
        {
               AllLines[x] = sr.ReadLine();
               x += 1;
        }
} //Finished. Close the file

//Now parallel process each line in the file
Parallel.For(0, AllLines.Length, x =>
{
    DoYourStuff(AllLines[x]); //do your work here
});
Freier Codierer 24
quelle
13

Verwenden Sie den folgenden Code:

foreach (string line in File.ReadAllLines(fileName))

Dies war ein großer Unterschied in der Leseleistung.

Es geht zu Lasten des Speicherverbrauchs, ist es aber absolut wert!

user2671536
quelle
Ich würde File.ReadLines (klick mich an) lieber alsFile.ReadAllLines
newbieguy
5

In der Frage zum Stapelüberlauf gibt es ein gutes Thema dazu. Ist die Rendite langsamer als die Rendite der alten Schule? .

Es sagt:

ReadAllLines lädt alle Zeilen in den Speicher und gibt eine Zeichenfolge [] zurück. Alles schön und gut, wenn die Datei klein ist. Wenn die Datei größer ist als in den Speicher passt, geht Ihnen der Speicher aus.

ReadLines verwendet dagegen Yield Return, um jeweils eine Zeile zurückzugeben. Damit können Sie Dateien jeder Größe lesen. Es wird nicht die gesamte Datei in den Speicher geladen.

Angenommen, Sie möchten die erste Zeile finden, die das Wort "foo" enthält, und dann beenden. Mit ReadAllLines müssten Sie die gesamte Datei in den Speicher lesen, auch wenn in der ersten Zeile "foo" vorkommt. Mit ReadLines lesen Sie nur eine Zeile. Welches wäre schneller?

Marcel James
quelle
4

Wenn die Dateigröße nicht groß ist, ist es schneller, die gesamte Datei zu lesen und anschließend zu teilen

var filestreams = sr.ReadToEnd().Split(Environment.NewLine, 
                              StringSplitOptions.RemoveEmptyEntries);
Saeed Amiri
quelle
6
File.ReadAllLines()
Jgauffin
@jgauffin Ich weiß nicht, was hinter der Implementierung von file.ReadAlllines () steckt, aber ich denke, es hat einen begrenzten Puffer und fileReadtoEnd-Puffer sollte größer sein, so dass die Anzahl der Zugriffe auf Dateien auf diese Weise verringert wird und string.Split in der Wenn die Dateigröße nicht groß ist, ist sie schneller als der Mehrfachzugriff auf Dateien.
Saeed Amiri
Ich bezweifle, File.ReadAllLinesdass eine feste Puffergröße haben, da die Dateigröße bekannt ist.
Jgauffin
1
@jgauffin: In .NET 4.0 File.ReadAllLineswird eine Liste erstellt und in einer Schleife mit StreamReader.ReadLine(mit möglicher Neuzuweisung des zugrunde liegenden Arrays) zu dieser Liste hinzugefügt . Diese Methode verwendet eine Standardpuffergröße von 1024. Dadurch wird StreamReader.ReadToEndder Zeilenanalyse-Teil vermieden und die Puffergröße kann bei Bedarf im Konstruktor festgelegt werden.
Martin Liversage
Es wäre hilfreich, "BIG" in Bezug auf die Dateigröße zu definieren.
Paul
2

Wenn Sie über genügend Speicher verfügen, habe ich einige Leistungssteigerungen festgestellt, indem ich die gesamte Datei in einen Speicherstrom eingelesen und daraufhin einen Stream-Reader geöffnet habe, um die Zeilen zu lesen. Solange Sie tatsächlich vorhaben, die gesamte Datei zu lesen, kann dies zu Verbesserungen führen.

Kibbee
quelle
1
File.ReadAllLinesscheint dann eine bessere Wahl zu sein.
Jgauffin
2

Sie können nicht schneller werden, wenn Sie eine vorhandene API zum Lesen der Zeilen verwenden möchten. Das Lesen größerer Blöcke und das manuelle Finden jeder neuen Zeile im Lesepuffer wäre jedoch wahrscheinlich schneller.

jgauffin
quelle