Ich habe die schöne Aufgabe, herauszufinden, wie man mit großen Dateien umgeht, die in den Skripteditor unserer Anwendung geladen werden (es ist wie VBA für unser internes Produkt für schnelle Makros). Die meisten Dateien haben eine Größe von 300-400 KB, was ein gutes Laden ist. Aber wenn sie über 100 MB hinausgehen, fällt es dem Prozess schwer (wie zu erwarten).
Was passiert ist, dass die Datei gelesen und in eine RichTextBox verschoben wird, die dann navigiert wird - machen Sie sich nicht zu viele Sorgen um diesen Teil.
Der Entwickler, der den ursprünglichen Code geschrieben hat, verwendet einfach einen StreamReader und tut dies
[Reader].ReadToEnd()
Das könnte eine ganze Weile dauern.
Meine Aufgabe ist es, diesen Code aufzubrechen, ihn in Blöcken in einen Puffer einzulesen und einen Fortschrittsbalken mit der Option zum Abbrechen anzuzeigen.
Einige Annahmen:
- Die meisten Dateien sind 30-40 MB groß
- Der Inhalt der Datei ist Text (nicht binär), einige sind im Unix-Format, einige sind DOS.
- Sobald der Inhalt abgerufen ist, ermitteln wir, welcher Terminator verwendet wird.
- Niemand ist besorgt, sobald es die Zeit geladen hat, die zum Rendern in der Richtextbox benötigt wird. Es ist nur das anfängliche Laden des Textes.
Nun zu den Fragen:
- Kann ich einfach StreamReader verwenden, dann die Length-Eigenschaft (also ProgressMax) überprüfen und einen Lesevorgang für eine festgelegte Puffergröße ausgeben und in einer while-Schleife WHILST in einem Hintergrund-Worker durchlaufen , damit der Haupt-UI-Thread nicht blockiert wird? Kehren Sie dann den Stringbuilder nach Abschluss zum Hauptthread zurück.
- Der Inhalt wird an einen StringBuilder gesendet. Kann ich den StringBuilder mit der Größe des Streams initialisieren, wenn die Länge verfügbar ist?
Sind das (Ihrer Meinung nach) gute Ideen? Ich hatte in der Vergangenheit einige Probleme beim Lesen von Inhalten aus Streams, da immer die letzten Bytes oder ähnliches fehlen, aber ich werde eine andere Frage stellen, wenn dies der Fall ist.
quelle
Antworten:
Sie können die Lesegeschwindigkeit verbessern, indem Sie einen BufferedStream wie folgt verwenden:
März 2013 UPDATE
Ich habe kürzlich Code zum Lesen und Verarbeiten (Suchen nach Text in) 1 GB-ischen Textdateien (viel größer als die hier beteiligten Dateien) geschrieben und durch die Verwendung eines Produzenten / Konsumenten-Musters einen signifikanten Leistungsgewinn erzielt. Die Produzentenaufgabe las Textzeilen mit dem ein
BufferedStream
und übergab sie an eine separate Verbraucheraufgabe, die die Suche durchführte.Ich nutzte dies als Gelegenheit, um den TPL-Datenfluss zu lernen, der sich sehr gut zum schnellen Codieren dieses Musters eignet.
Warum BufferedStream schneller ist
Dezember 2014 UPDATE: Ihr Kilometerstand kann variieren
Basierend auf den Kommentaren, sollte Filestream eine Verwendung sein BufferedStream intern. Als diese Antwort zum ersten Mal gegeben wurde, habe ich durch Hinzufügen eines BufferedStream einen signifikanten Leistungsschub gemessen. Zu der Zeit zielte ich auf .NET 3.x auf einer 32-Bit-Plattform. Wenn ich heute .NET 4.5 auf einer 64-Bit-Plattform ausrichte, sehe ich keine Verbesserung.
verbunden
Ich bin auf einen Fall gestoßen, in dem das Streaming einer großen, generierten CSV-Datei von einer ASP.Net MVC-Aktion in den Antwort-Stream sehr langsam war. Durch Hinzufügen eines BufferedStream wurde die Leistung in diesem Fall um das 100-fache verbessert. Weitere Informationen finden Sie unter Ungepufferte Ausgabe sehr langsam
quelle
Wenn Sie die Leistungs- und Benchmark-Statistiken auf dieser Website lesen , werden Sie feststellen, dass der schnellste Weg zum Lesen einer Textdatei (da Lesen, Schreiben und Verarbeiten unterschiedlich sind) der folgende Codeausschnitt ist:
Alle bis zu 9 verschiedenen Methoden wurden mit einem Benchmark versehen, aber diese scheint die meiste Zeit die Nase vorn zu haben, selbst wenn der gepufferte Leser ausgeführt wird, wie andere Leser erwähnt haben.
quelle
StringBuilder
zum Laden in den Speicher verwenden, die schneller geladen werden, da nicht jedes Mal, wenn Sie Zeichen hinzufügen, eine neue Zeichenfolge erstellt wird)Sie sagen, Sie wurden aufgefordert, einen Fortschrittsbalken anzuzeigen, während eine große Datei geladen wird. Liegt das daran, dass die Benutzer wirklich den genauen Prozentsatz des Ladens von Dateien sehen möchten, oder nur daran, dass sie visuelles Feedback wünschen, dass etwas passiert?
Wenn letzteres zutrifft, wird die Lösung viel einfacher. Führen Sie einfach
reader.ReadToEnd()
einen Hintergrund-Thread aus und zeigen Sie einen Fortschrittsbalken vom Typ Laufschrift anstelle eines richtigen an.Ich spreche diesen Punkt an, weil dies meiner Erfahrung nach häufig der Fall ist. Wenn Sie ein Datenverarbeitungsprogramm schreiben, sind Benutzer definitiv an einer% vollständigen Zahl interessiert. Bei einfachen, aber langsamen Aktualisierungen der Benutzeroberfläche möchten sie eher wissen, dass der Computer nicht abgestürzt ist. :-)
quelle
StreamReader
Laufenden. Es wird jedoch immer noch einfacher, da Sie nicht weiterlesen müssen, um die Fortschrittsanzeige zu berechnen.Für Binärdateien ist dies der schnellste Weg, sie zu lesen.
In meinen Tests ist es hunderte Male schneller.
quelle
Verwenden Sie einen Hintergrundarbeiter und lesen Sie nur eine begrenzte Anzahl von Zeilen. Lesen Sie mehr nur, wenn der Benutzer einen Bildlauf durchführt.
Und versuchen Sie niemals, ReadToEnd () zu verwenden. Es ist eine der Funktionen, von denen Sie denken, "warum haben sie es geschafft?"; Es ist ein Skript-Kinderhelfer , der gut zu kleinen Dingen passt , aber wie Sie sehen, ist es für große Dateien zum Kotzen ...
Die Leute, die dir sagen, dass du StringBuilder verwenden sollst, müssen die MSDN öfter lesen:
Leistungsüberlegungen
Die Methoden Concat und AppendFormat verketten neue Daten mit einem vorhandenen String- oder StringBuilder-Objekt. Eine Verkettungsoperation für Zeichenfolgenobjekte erstellt immer ein neues Objekt aus der vorhandenen Zeichenfolge und den neuen Daten. Ein StringBuilder-Objekt verwaltet einen Puffer, um die Verkettung neuer Daten zu ermöglichen. Neue Daten werden an das Ende des Puffers angehängt, wenn Platz verfügbar ist. Andernfalls wird ein neuer, größerer Puffer zugewiesen, Daten aus dem ursprünglichen Puffer werden in den neuen Puffer kopiert, und die neuen Daten werden an den neuen Puffer angehängt. Die Leistung einer Verkettungsoperation für einen String oder ein StringBuilder-Objekt hängt davon ab, wie oft eine Speicherzuweisung erfolgt.
Eine String-Verkettungsoperation weist immer Speicher zu, während eine StringBuilder-Verkettungsoperation nur Speicher zuweist, wenn der StringBuilder-Objektpuffer zu klein ist, um die neuen Daten aufzunehmen. Folglich ist die String-Klasse für eine Verkettungsoperation vorzuziehen, wenn eine feste Anzahl von String-Objekten verkettet ist. In diesem Fall können die einzelnen Verkettungsoperationen vom Compiler sogar zu einer einzigen Operation kombiniert werden. Ein StringBuilder-Objekt ist für eine Verkettungsoperation vorzuziehen, wenn eine beliebige Anzahl von Zeichenfolgen verkettet wird. Zum Beispiel, wenn eine Schleife eine zufällige Anzahl von Zeichenfolgen von Benutzereingaben verkettet.
Das bedeutet eine enorme Speicherzuweisung, was zu einer großen Nutzung des Swap-Dateisystems führt, das Teile Ihres Festplattenlaufwerks simuliert, um sich wie der RAM-Speicher zu verhalten, aber ein Festplattenlaufwerk ist sehr langsam.
Die StringBuilder-Option sieht gut aus, wenn Sie das System als Mono-Benutzer verwenden. Wenn jedoch zwei oder mehr Benutzer gleichzeitig große Dateien lesen, tritt ein Problem auf.
quelle
Dies sollte ausreichen, um Ihnen den Einstieg zu erleichtern.
quelle
Schauen Sie sich das folgende Code-Snippet an. Sie haben erwähnt
Most files will be 30-40 MB
. Dies behauptet, 180 MB in 1,4 Sekunden auf einem Intel Quad Core zu lesen:Originaler Artikel
quelle
Sie könnten besser dran zu verwenden Memory-Mapped - Dateien verarbeitet werden hier .. Die Memory - Mapped - Datei - Unterstützung in .NET 4 sein um wird (ich glaube ... ich hörte , dass durch jemand anderes darüber zu reden), damit dieser Wrapper , der p verwendet Ich rufe auf, um den gleichen Job zu machen.
Bearbeiten: Sehen Sie hier auf dem MSDN, wie es funktioniert. Hier ist der Blogeintrag, der angibt, wie es in der kommenden .NET 4-Version gemacht wird, wenn es als Release herauskommt. Der Link, den ich zuvor gegeben habe, ist ein Wrapper um den Pinvoke, um dies zu erreichen. Sie können die gesamte Datei dem Speicher zuordnen und sie beim Scrollen durch die Datei wie ein Schiebefenster anzeigen.
quelle
Alles gute Antworten! Für jemanden, der nach einer Antwort sucht, scheinen diese jedoch etwas unvollständig zu sein.
Da ein Standard-String je nach Konfiguration nur die Größe X, 2 GB bis 4 GB haben kann, erfüllen diese Antworten die Frage des OP nicht wirklich. Eine Methode besteht darin, mit einer Liste von Zeichenfolgen zu arbeiten:
Einige möchten möglicherweise die Zeile bei der Verarbeitung markieren und teilen. Die Zeichenfolgenliste kann jetzt sehr große Textmengen enthalten.
quelle
Ein Iterator könnte für diese Art von Arbeit perfekt sein:
Sie können es wie folgt aufrufen:
Beim Laden der Datei gibt der Iterator die Fortschrittsnummer von 0 bis 100 zurück, mit der Sie Ihren Fortschrittsbalken aktualisieren können. Sobald die Schleife beendet ist, enthält der StringBuilder den Inhalt der Textdatei.
Da Sie Text möchten, können Sie auch BinaryReader zum Einlesen von Zeichen verwenden, um sicherzustellen, dass Ihre Puffer beim Lesen von Mehrbyte-Zeichen ( UTF-8 , UTF-16 usw.) korrekt ausgerichtet sind .
Dies alles geschieht ohne Hintergrundaufgaben, Threads oder komplexe benutzerdefinierte Zustandsautomaten.
quelle
Meine Datei ist über 13 GB groß:
Der folgende Link enthält den Code, mit dem eine Datei leicht gelesen werden kann:
Lesen Sie eine große Textdatei
Mehr Informationen
quelle