Große Datenmengen effizient in einen Text File schreiben

  • VB.NET

Es gibt 51 Antworten in diesem Thema. Der letzte Beitrag () ist von Peter329.

    Große Datenmengen effizient in einen Text File schreiben

    Hi,

    ich lese Verzeichnisbäume aus und möchte die Daten in einen Text File ausgeben.

    Das hab ich (im Prinzip) wie folgt versucht

    VB.NET-Quellcode

    1. 'Process all entries
    2. For Each RealEntry In RealEntryList
    3. My.Computer.FileSystem.WriteAllText(IMAGEFILENAMENEW, & RealEntry & vbCrLf, True)
    4. Next


    Allerdings können im Rahmen des Verfahrens (rekursiv) bis zu 300.000 Einträge anfallen. Und da ist dann die Methode "WriteAllText" ganz offensichtlich im Hinblick auf die Performace restlos überfordert.

    Wie schreibe ich denn große Datenmengen effizient in einen Text File?

    Im Anschluss daran will ich die Daten zeilenweise mit einem ähnlich großen File abgleichen. Da stellt sich dann wohl das gleiche Problem für das Lesen von großen Datenmengen.

    LG
    Peter
    In welcher Form liegen denn die

    Peter329 schrieb:

    Verzeichnisbäume
    vor?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    Peter329 schrieb:

    beispielsweise
    Missverständnis. :/
    In welcher datenstruktur in Deinem Programm liegen sie vor?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    VB.NET-Quellcode

    1. RealEntryList = Directory.GetFileSystemEntries(RealPath)


    So hole ich mir die Listen ... wie schon gesagt, mit rekursivem Aufruf. Die jeweiligen Einträge werden dann bearbeitet und ergeben einen String .... und den will ich dann in einen Text File schreiben. Und weil Windows so ein Moloch ist, können sind das rund 300.000 Zeilen.



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

    @Peter329 Probier mal, die in ein Array / List(Of String) zu packen, die kannst Du mit IO.File.WriteAllLines / IO.File.ReadAllLines / IO.File.ReadLines bearbeiten.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Wo soll der unterschied sein? Wenn du vorhast den UI Thread nicht zu blockieren dann kannst du async/await oder andere Methoden verwenden.
    Und wenn du so auf Performance aus bist solltest du evtl. auf Directory.EnumerateFileSystemEntries umsteigen. Außerdem gibt es bereits einen Overload für eine Rekursive-Suche, brauchst du also nicht selbst nachbasteln.

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

    Alle 300.000 Zeilen auf einen Schlag?
    naja, das wesentliche an Streams ist, dass man nichts auf einen Schlag tut, sondern du kannst hineinschreiben so viel du willst.
    Ums Puffern - damit da nicht für jeden Eintrag die Festplatte einzeln angesteuert wird (wies bei dir derzeit noch ist) - kümmert sich der Stream.
    Also - wie pinki schon sagte: Nutze einen StreamWriter (da ist der Stream dann schon mit drin).
    Guck dir im Objectbrowser die Methoden an, die verfügbar sind - eiglich erklärt sich das alles selbst.
    Halt noch am Ende des Gesamt-Schreibvorganges den Stream wieder disposen.

    Peter329 schrieb:

    blockweise
    gugst Du

    RodFromGermany schrieb:

    IO.File.ReadLines
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    Pinki schrieb:

    Und wenn du so auf Performance aus bist solltest du evtl. auf Directory.EnumerateFileSystemEntries umsteigen. Außerdem gibt es bereits einen Overload für eine Rekursive-Suche, brauchst du also nicht selbst nachbasteln.


    Tatsächlich ist das wohl so, dass meine rekursive Suche das Problem ist. Dein Verfahren läuft im Gegensatz dazu geradezu blitzartig!

    VB.NET-Quellcode

    1. Try
    2. Dim dirPath As String = ... 'Pfad eintragen
    3. Dim dirs As List(Of String) = _
    4. New List(Of String)(Directory.EnumerateDirectories(dirPath, "*", SearchOption.AllDirectories))
    5. For Each folder In dirs
    6. strImageBuffer &= folder & vbCrLf
    7. Next
    8. cntDirectories = dirs.Count
    9. Catch UAEx As UnauthorizedAccessException
    10. MsgBox(UAEx.Message)
    11. Exit Sub
    12. Catch PathEx As PathTooLongException
    13. MsgBox(PathEx.Message)
    14. Exit Sub
    15. End Try


    ABER ... leider klappt das etwa beim Pfad C:\ nicht, weil man da auf unauthorized Exceptions trifft. Dann wirft die Routine das Handtuch und gibt ÜBERHAUPT NICHTS aus.

    Kann man das so gestalten, dass Exceptions einfach ignoriert werden?
    stackoverflow.com/a/13954763

    VB.NET-Quellcode

    1. Public Function EnumerateDirectories(parentDirectory As String, searchPattern As String, searchOpt As SearchOption) As IEnumerable(Of String)
    2. Try
    3. Dim directories = Enumerable.Empty(Of String)()
    4. If searchOpt = SearchOption.AllDirectories Then
    5. directories = Directory.EnumerateDirectories(parentDirectory).SelectMany(Function(x) EnumerateDirectories(x, searchPattern, searchOpt))
    6. End If
    7. Return directories.Concat(Directory.EnumerateDirectories(parentDirectory, searchPattern))
    8. Catch ex As UnauthorizedAccessException
    9. Return Enumerable.Empty(Of String)()
    10. End Try
    11. End Function
    So, da bin ich wieder! :) Da hab ich jetzt doch eine Weile arbeiten müssen, um eure Ratschläge (vielen Dank!) mal umzusetzen.

    1. Enumeration vs. Rekursives Auslesen (unter Berücksichtigung der Unauthorized Access Exceptions)

    Ich habe beide Routinen mal ausprobiert. Das Befüllen der Rückgabeliste der Verzeichnisnamen habe ich in beiden Fällen erst mal deaktiviert.

    VB.NET-Quellcode

    1. Public Function EnumerateDirectories(parentDirectory As String,
    2. searchPattern As String,
    3. searchOpt As SearchOption) As IEnumerable(Of String)
    4. Try
    5. Dim directories = Enumerable.Empty(Of String)()
    6. If searchOpt = SearchOption.AllDirectories Then
    7. directories = _
    8. Directory.EnumerateDirectories(parentDirectory).SelectMany(Function(x) EnumerateDirectories(x, searchPattern, searchOpt))
    9. End If
    10. 'strImageBuffer &= "D " & parentDirectory & vbCrLf
    11. cntDirs += 1
    12. Return directories.Concat(Directory.EnumerateDirectories(parentDirectory, searchPattern)) 'Recursive call for directories
    13. Catch ex As UnauthorizedAccessException
    14. Debug.Print("UAEx: " & parentDirectory)
    15. cntUAEx += 1
    16. Return Enumerable.Empty(Of String)()
    17. End Try
    18. End Function
    19. Private Sub ProcessRecursively(parentDirectory As String, intLevel As Integer)
    20. Dim RealEntryList As String()
    21. Try
    22. RealEntryList = Directory.GetDirectories(parentDirectory)
    23. 'strImageBuffer &= "D " & parentDirectory & vbCrLf
    24. cntDirs += 1
    25. Catch ex As Exception
    26. Debug.Print("UAEx: " & parentDirectory)
    27. cntUAEx += 1
    28. Exit Sub
    29. End Try
    30. For Each childEntry In RealEntryList
    31. ProcessRecursively(childEntry, intLevel + 1) 'Recursive call for directories
    32. Next
    33. End Sub


    Erstaunlicherweise ist meine Rekursion schneller als die Enumeration:

    Algorithm: ENUM
    Dirs : 42134
    UAEx : 548
    BuffLen : 0 Bytes
    Elapsed : 7 seconds

    Algorithm: RECURSIVE
    Dirs : 42134
    UAEx : 548
    BuffLen : 0 Bytes
    Elapsed : 6 seconds

    Der Unterschied ist aber nicht weltbewegend.

    2. Ausschreiben der Verzeichnisnamen auf den Ausgabefile

    Das Problem ist der Aufbau des Rückgabestrings "strImageBuffer". Denn wenn man diese Anweisung aktiviert, wird die Sache abendfüllend!

    Algorithm: ENUM
    Dirs : 42134
    UAEx : 548
    BuffLen : 3.994.995 Bytes
    Elapsed : 87 seconds

    Algorithm: RECURSIVE
    Dirs : 42134
    UAEx : 548
    BuffLen : 3.994.995 Bytes
    Elapsed : 74 seconds

    Offensichtlich ist das Aufbauen des knapp 4 GB langen Strings das Problem!

    Ausschreiben tue ich den String dann mit

    VB.NET-Quellcode

    1. Using fs As FileStream = New FileStream(IMAGEFILEPATHNEW, FileMode.CreateNew)
    2. Using writer As StreamWriter = New StreamWriter(fs)
    3. writer.Write(strImageBuffer)
    4. End Using
    5. End Using


    Und das dauert weniger als 1 Sekunde.

    Lange Rede, kurzer Sinn: Ich muss wohl den StreamWriter anders bedienen! Vermutlich muss ich statt den strImageBuffer zu verketten, jeden Directory Name sofort an den StreamWriter übergeben. Aber das geschieht ja auf ganz verschiedenen Ebenen der Rekursion. Vermutlich muss ich den StreamWriter also als Parameter übergeben.

    Richtig soweit? Und wenn das so sein sollte, wie mache ich das dann ???
    Wie genau verarbeitest du denn die Strings? Optimal wäre natürlich so(Pseudcode):

    Quellcode

    1. new StreamWriter
    2. for each entry in EnumerateDirectories(...)
    3. doSomething(entry)
    4. StreamWriter.WriteLine(entry)


    Der Grund wieso ich dir EnumerateDirectories vorgeschlagen habe ist folgender: EnumerateDirectories returned ein IEnumerable statt ein komplettes Array, so musst du nicht darauf warten dass das Array komplett ist stattdessen werden die Entries erst geladen wenn darauf zugegriffen wird(mit for each z.B.). So musst du 1. nicht warten bis ein Array fertig gefüllt ist und 2. nicht unnötig RAM verschwenden.
    Natürlich musst du deinen Code auch dafür anpassen sonst macht es wenig sinn.
    Hier noch eine Anregung von mir.
    Das Teil habe ich mal gebraucht und es erzeugt eine XML-Struktur und das auch ganz fix.
    Habe es mal auf "C:" losgelassen, ging zügig.

    Hier als Test-Console-App:
    Spoiler anzeigen

    C#-Quellcode

    1. using System;
    2. using System.IO;
    3. using System.Xml.Linq;
    4. namespace DirDOM
    5. {
    6. class Program
    7. {
    8. static void Main(string[] args)
    9. {
    10. var dir = new DirectoryInfo(@"C:\");
    11. var doc = new XDocument(GetDirectoryXml(dir));
    12. doc.Save(@"D:\dom.xml");
    13. Console.Write("-- press any key --");
    14. Console.ReadKey();
    15. }
    16. /// <summary>
    17. /// GetDirectoryXml
    18. /// </summary>
    19. /// <param name="dir">DirectoryInfo</param>
    20. /// <returns>XElement</returns>
    21. private static XElement GetDirectoryXml(DirectoryInfo dir)
    22. {
    23. try
    24. {
    25. var info = new XElement("dir", new XAttribute("name", dir.Name), new XAttribute("attr", dir.Attributes));
    26. foreach (var file in dir.GetFiles())
    27. info.Add(new XElement("file", new XAttribute("name", file.Name)));
    28. foreach (var subDir in dir.GetDirectories())
    29. info.Add(GetDirectoryXml(subDir));
    30. return info;
    31. }
    32. catch
    33. {
    34. return new XElement("noaccess", new XAttribute("name", dir.Name));
    35. }
    36. }
    37. }
    38. }

    Peter329 schrieb:

    Offensichtlich ist das Aufbauen des knapp 4 GB langen Strings das Problem!
    Wohl war!
    Natürlich darfst keinen solchen Monster-String aufbauen, sondern einfach jeden EinzelString direkt in den StreamWriter schreiben.
    Streamwriter beinhaltet selbst eine Pufferung, die ist auch sehr gut, aber zur Not kann man da sogar noch mit Threading optimieren, aber probier besser erstmal normal.
    (Es sei denn, die Geschichte läuft eh im Nebenthread ab)
    Also, Monster String aufbauen iss nich ... das habe ich verstanden.

    Ich übergebe jetzt den Stream als Parameter und schreibe direkt! Und das funktioniert! :D Man glaubt es kaum !

    Hier der aktuelle Benchmark:

    Algorithm: RECURSIVE
    Dirs : 42134
    UAEx : 548
    BuffLen : 3.994.995 Bytes
    Elapsed : 5 seconds

    Ihr habt mir alle wirklich SEHR geholfen! Ganz herzlichen Dank, ich habe eine Menge gelernt!

    Den Rest der Routine sollte ich jetzt wieder alleine hinkriegen!

    LG
    Peter

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „Peter329“ ()