sehr große Textdatei (ca. 400MB) einlesen, bearbeiten und wieder speichern

  • VB.NET

Es gibt 24 Antworten in diesem Thema. Der letzte Beitrag () ist von tina83.

    sehr große Textdatei (ca. 400MB) einlesen, bearbeiten und wieder speichern

    Hallo zusammen,

    ich bräuchte mal eure Ratschläge wie man mit sehr großen Textdateien umgehen kann.
    Meine Textdateien können schon 400MB groß sein. Jede Zeile besteht aus mindestens 3 Spalten, die durch Leerzeichen getrennt sind. Eine Datei kann schon mal mehrere Millionen Zeilen haben. Meine bisher größte Datei hatte 19 Mio. Zeilen. Hier ein kleiner Ausschnitt:

    0.000 -2.274 0.577
    0.000 -2.276 0.577
    0.000 -2.278 0.576
    0.000 -2.280 0.576
    0.000 -2.280 0.576

    Mein Ziel ist es, die Datei einzulesen, in Spalten aufzuteilen, mit der dritten Spalte Berechnungen durchführen, die erste Spalte mit anderen Zahlen ersetzen und das ganze dann wieder in einer Textdatei zu speichern.

    Mit meiner jetzigen Vorgehensweise klappt das auch wunderbar. Das Problem ist nur der Arbeitsspeicher. 4GB reichen nur für kleine Dateien (vllt. bis zu 150MB), 8GB RAM sind schon besser aus und es gehen auch Dateien mit einer Größe von 400MB.
    Nachdem ich das Speicherplatzproblem festgestellt habe, lasse ich keine Daten mehr in einer Datagridview anzeigen. Alles läuft im Hintergrund. Mit einer DGV dauert allein das Einlesen 1 Stunde. Ohne DGV läuft alles wenigstens innerhalb weniger Minuten, aber nur bei 8GB RAM. Nun lese ich alles in eine Datatable ein.

    Mein Code zum Einlesen sieht folgendermaßen aus:

    VB.NET-Quellcode

    1. Function ascii_einlesen() As DataTable
    2. Dim lines() As String = System.IO.File.ReadAllLines(OpenFileDialog1.FileName, System.Text.Encoding.Default)
    3. 'Strings (aus txt-Datei mit Punkt) in Single mit Komma umwandeln
    4. Dim style As System.Globalization.NumberStyles = Globalization.NumberStyles.Any
    5. Dim culture As System.Globalization.CultureInfo = System.Globalization.CultureInfo.InvariantCulture
    6. Dim ende As Integer = lines.Count - 1
    7. ProgressBar1.Maximum = ende
    8. Dim table As DataTable = New DataTable
    9. table.Columns.Add("x", GetType(Double))
    10. table.Columns.Add("y", GetType(Double))
    11. table.Columns.Add("z", GetType(Double))
    12. table.Columns.Add("Berechnung", GetType(Double))
    13. For i = 0 To ende
    14. Dim spalte() As String = lines(i).Split(" ")
    15. Single.TryParse(spalte(0), style, culture, spalte(0))
    16. Single.TryParse(spalte(1), style, culture, spalte(1))
    17. Single.TryParse(spalte(2), style, culture, spalte(2))
    18. table.Rows.Add(spalte(0), spalte(1), spalte(2), 0)
    19. ProgressBar1.Value = i
    20. Next i
    21. Return table
    22. End Function

    Welche Alternative gibts noch zu ner Datatable, die etwas mehr unabhängiger vom Arbeitsspeicher ist?
    Mittlerweile bin ich auch offen für andere Programmiersprachen, wenn die mehr versprechen bzw. besser mit solchen Datenmengen umgehen können ohne den ganzen Rechner lahmzulegen :whistling:

    Vielen Dank für eure Hilfe
    Gruß
    Wie verarbeitest du denn die einzelnen Zeilen der Datei?
    Wenn du in den Zeilen ansich bleibst, kannst du doch über einen StreamReader alle Zeilen auslesen und verarbeiten.

    Quellcode

    1. StreamReader streamReader = New StreamReader(DeineDatei)
    2. While Not streamReader.EndOfStream
    3. Dim spalten() As String = streamReader.ReadLine().Split(" ")
    4. ' .....
    5. End While

    So hast du immer nur eine einzige Zeile im Arbeitsspeicher und solltest somit kein Problem damit haben.
    in der 3. Spalte ziehe ich immer nur den Wert vom Wert in der Spalte davor ab. Und das Ergebnis kommt in die vierte leere Spalte. Dort such ich die Werte, die größer eines Grenzwertes sind und diese Zeilenpositionen (also in welcher Zeile der Grenzwert vorkommt) speicher ich in einem Array. Damit kann ich dann die erste Spalte neu belegen.
    Also eigentlich ist alles nur ein mehrmaliges Durchlaufen der gesamten Datei.

    EDIT: ich ziehe den Wert von der Zeile davor ab, nicht von der Spalte davor -> das bedeutet: ich brauche immer 2 Zeilen

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „tina83“ ()

    Dann lies doch nur zeilenweise ein, verarbeite alles und schreibe die Datei wieder neu. Dann wieder neue Zeile einlesen...
    EDIT: Ich dachte erst, du verrechnest die Spaltenwerte untereinander (aus der gleichen Spalte, nur unterschiedliche Zeilen).
    EDIT2: Hm, das Schreiben von 19 Mio Zeilen könnte auch zu lange dauern...

    tina83 schrieb:


    EDIT: ich ziehe den Wert von der Zeile davor ab, nicht von der Spalte davor


    SO habe ich es komischerweise auch erst verstanden. :) Musste 2 Mal lesen. Dann liest du eben 2 Zeilen hintereinander ein und verrechnest es.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „sonne75“ ()

    Nein, ich meinte, wenn du immer 2 Zeilen auf einmal einliest, Daten veränderst und die Datei jeweils wieder speicherst. Ich schaue mal, ob es eine Funktion gibt, in einer Datei nur an einer bestimmten Zeile zu speichern.

    Dein Problem ist ja weniger das Verarbeiten (das kann man wunderbar zeilenweise machen), sondern das Speichern danach...
    Ich glaube, Streamwriter schreibt auch jeweils zeilenweise (musst natürlich in eine andere Datei schreiben als du liest).

    Und klar, wenn es schon im Speicher ist, hast du danach keine Probleme mehr.

    Probier es doch aus, dass du zweimal hintereinander Daten liest:

    VB.NET-Quellcode

    1. Dim objWriter As New Streamwriter(path1)
    2. Using objReader As New Streamreader(path)
    3. Do Until objReader.EndOfStream
    4. Dim str1 = objReader.ReadLine()
    5. Dim str2 = objReader.ReadLine()
    6. .......
    7. Dim str3=....
    8. Dim str4=...
    9. objWriter.Writeline(str3)
    10. objWriter.Writeline(str4)
    11. Loop
    12. objReader.Close()
    13. objWriter.Close()
    14. End Using
    15. objWriter.Dispose()


    Irgendwie so ausprobieren.

    VincentTB schrieb:

    Bezieht sich die Diskussion auf mich? Ich wollte nur anmerken, dass es unnötig ist, ein Objekt mehrmals zu verwerfen. Was hat das mit dem Pfad zu tun?


    Warum ein Objekt? Wenn ich unterschiedlichen Pfad übergebe, dann sind es doch 2 Objekte? Eins ein Streamreader von der Datei unter "path" und eins ein Streamwriter von der Datei unter "path1". Deswegen hat das sehr wohl was mit den Pfaden zu tun...

    Oder worauf bezog sich bei dir "ein Objekt"? Auf objWriter.Close() und objWriter.Dispose()?
    Hä, bin ich jetzt blöd?

    VB.NET-Quellcode

    1. Dim objWriter As New Streamwriter(path1) 'Erstes Objekt
    2. Using objReader As New Streamreader(path) 'Zweites Objekt
    3. Do Until objReader.EndOfStream
    4. Dim str1 = objReader.ReadLine()
    5. Dim str2 = objReader.ReadLine()
    6. .......
    7. Dim str3=....
    8. Dim str4=...
    9. objWriter.Writeline(str3)
    10. objWriter.Writeline(str4)
    11. Loop
    12. objReader.Close() 'Zweites Objekt 1. Mal geschlossen
    13. objWriter.Close() 'Erstes Objekt 1. Mal geschlossen
    14. End Using 'Zweites Objekt zweites mal geschlossen
    15. objWriter.Dispose() 'Erstes Objekt zweites mal geschlossen


    Du schließt (wirfst weg) doch beide Objekte 2 mal?!
    Mfg
    Vincent

    Ich dachte, mit "Close()" wird die Datei geschrieben und geschlossen. Und mit "End Using" oder "Dispose()" (weil ich keine verschachtelte Using machen wollte, wäre aber wohl gegangen) das Objekt selbst. :?:
    Ich verwende es so in meinem Code und habe es so von anderen Codes abgeleitet. Lasse mich aber gerne Besseres belehren.

    EDIT: Hast Recht
    Public Overrides Sub Close()
    Member von System.IO.StreamReader
    Zusammenfassung:
    Schließt das System.IO.StreamReader-Objekt sowie den zugrunde liegenden Stream und gibt alle dem Reader zugeordneten Systemressourcen frei.



    Dann werde ich das bei mir in anderen Programmen ohne "Using" oder "Dispose" machen... Aber warum verwenden dann viele "Using" bei Streamreader? "Close()" muss man ja in jedem Fall machen, wenn man die Datei speichern will.
    Habe gerade in meinem Code nachgeschaut, muss man doch nicht. Keine Ahnung mehr, wo ich das her habe. :?: Aber gut, dass wir darüber geredet haben, jetzt merke ich es mir. :thumbup:

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „sonne75“ ()

    Using bzw. Dispose sind fast das gleiche wie Close():
    Spoiler anzeigen

    Microsoft Visual Basic 2005 - Das Entwicklerbuch schrieb:

    klären wir zunächst die Frage: Wozu braucht diese Klasse überhaupt ein Finalize und ein Dispose?
    Die Klasse verwendet ein MemoryStream-Objekt. Und: Die Klasse schreibt zunächst in diesen »Dateistrom«, aber erst bei der Ausführung von Close den erstellten Datenstrom in eine Datei. Wenn Sie
    Daten in einen Datenstrom hineinschreiben, dann müssen Sie sicherstellen, dass Daten, die sich dort
    befinden, sich später auch bis aufs letzte Byte in der angegebenen Datei befinden. Das gewährleistet
    während des Close- bzw. Dispose-Vorgangs die Zeile, die Sie im oben stehenden Listing in fetter
    Schrift sehen.
    Nun öffnet unsere Klasse ein MemoryStream-Objekt automatisch, wenn der Entwickler, der die Klasse
    verwendet, eine der beiden Methoden OpenForWriting oder OpenForReading verwendet. Er kann nun mit
    SaveObject bzw. LoadObjectden Datenstrom verwenden. Er muss anschließend aber auch – und jetzt
    kommt der IDisposable-Pattern ins Spiel – dafür sorgen, dass alles wieder geschlossen wird, nur dann
    wird – und das ist wichtig im Falle des Schreibens – der zunächst im Speicher angelegte Datenstrom
    mit den Objektdaten tatsächlich in die Datei geschrieben. Macht er es nicht, dann sollte unsere
    Klasse intelligent genug sein, um zu retten, was zu retten ist. Die Klasse sorgt also für das Speichern
    des Speicherdatenstroms, wenn …
    ■ … der Entwickler die Dispose-Methode (oder die Close-Methode – das ist in diesem Fall dasselbe),
    so wie es sein sollte, selbst aufruft, oder
    ■ … der Entwickler es vergessen hat, aber der Garbage Collector uns durch den Aufruf von Finalize
    anzeigt, dass die Klasse zur Entsorgung ansteht, und spätestens jetzt alle verwendeten Ressourcen
    möglichst schnell aufgeräumt und freigegeben werden sollten.
    Mfg
    Vincent