Benutzer mit den meisten Antworten
File.WriteAllText() vs. StreamWriter.WriteLine()

Frage
-
Hallo,
Ich habe mal eine allgemeine Frage zu File.WriteAllText und StreamWriter.WriteLine.
In einem Projekt erstelle ich auf Basis von Daten aus einer SQL-Datenbank eine CSV. Diese Daten lese ich über einen SqlDataReader Datensatz für Datensatz aus und schreibe die Daten für die CSV formatiert in die Zieldatei.
Nun sind es mittlerweile ~190.000 Datensätze und die File.WriteAllText() Variante benötigt aktuell ca. 5min, um die CSV zu erstellen. Hier habe ich jetzt mal auf StreamWriter.WriteLine() umgestellt und die Datei ist in < 2s erstellt?!
Dass es evtl. einen Unterschied gibt, hatte ich erwartet, aber dass der so immens ist, sicher nicht.
Woran liegt das? Ich dachte immer die File.Write-Methoden greifen auf den StreamWriter zurück, oder bin ich da falsch informiert?
Kann mir jemand auf die Sprünge helfen, warum der Unterschied so gewaltig ist?
Ich habe bisher in vielen Projekten der Einfachheit zu File gegriffen und überlege nun, diese auf StreamWriter umzuschreiben.
Danke für Infos
Gruß Arne
Antworten
-
Hallo Arne,
warum unterscheidest Du zwischen der ersten und den restlichen Zeilen und wendest mal WriteAllText und mal AppendAllText an? Das macht eigentlich keinen Sinn. AppendAllText erzeugt die Datei auch, wenn sie noch nicht existiert, also kannst Du auch gleich überall AppendAllText nehmen.
Zu deiner Frage der Vorgehensweise von AppendAllText: Genau das passiert dabei.
MSDN Doku - File.AppendAllText Methode
Öffnet eine Datei, fügt die angegebene Zeichenfolge an die Datei an und schließt dann die Datei.Wenn Du aber x Zeilen schreiben willst, macht es eigentlich keinen Sinn, das einzeln zu machen. Sammel dir die Inhalte daher über einen StringBuilder in einen String und schreib den dann weg.
Der Unterschied zu StreamWriter.WriteLine ist u.a. dass File.AppendAllText, WriteAllText und einige andere Methoden in der Richtung die Datei direkt schreiben. StreamWriter.WriteLine schreibt erst auf Anforderung, bspw. beim Aufruf von StreamWriter.Close (kann auch implizit, bspw. durch using ... passieren)
Daher ist letzteres natürlich erheblich schneller.
Gruß, Stefan
Microsoft MVP - Visual Developer ASP/ASP.NET (2001-2018)
https://www.asp-solutions.de/ - IT Beratung, Softwareentwicklung, Remotesupport- Als Antwort markiert Arne Drews Mittwoch, 13. Februar 2019 07:31
Alle Antworten
-
Wie WriteAllText funktioniert kannst du bei Reference Source nachschauen. Ja, es benutzt auch einfach nur den StreamWriter.
Für WriteAllText brauchst du allerdings den kompletten Dateiinhalt als String, im Gegensatz zu WriteLine. Wie setzt du die Zeilen für WriteAllText für die gesamte Datei zusammen? Ich würde hier den Flaschenhals vermuten.
Wenn du zum Beispiel etwas in dieser Art machst, würde es mich nicht wundern:
string file = string.Empty; foreach(string line in lines) { file += line + Environment.NewLine; }
Dagegen sollte es hiermit sehr schnell gehen:
string file = string.Join(Environment.NewLine, lines);
-
Hi Arne,
das von Dir bemerkte Verhalten kann die Ursache in der Arbeit mit Zeichenketten haben. Wenn Du 190.000 Datensätze Satz für Satz einliest und dabei immer eine Zeichenkette um zusätzliche Daten erweiterst (Datensatzinhalt anhängst), dann wird jedes Mal neuer Speicherplatz in der neuen Länge reserviert, der alte Inhalt dorthin kopiert, der anzuhängende Inhalt auch dorthin kopiert und der alte Speicherplatz freigegeben.Hier mal eine Konsolen-Demo, in der Zeichenketten verkettet werden und alternativ der StringBuilder genutzt wird, der über "vorauseilende" Reservierung von Speicherplatz nicht bei jedem Hinzufügen neuen Speicherplatz mit Kopieren ausführt:
using System; using System.Diagnostics; using System.Text; namespace ConsoleApp1 { class Program1 { static void Main(string[] args) { try { Demo c = new Demo(); c.Execute(); } catch (Exception ex) { Console.WriteLine(ex.ToString()); } Console.WriteLine("Fertig, Abschluss mit beliebiger Taste"); Console.ReadKey(); } class Demo { internal void Execute() { Stopwatch sw = new Stopwatch(); // Test 1 string str = string.Empty; sw.Start(); for (int i = 0; i < 190000; i++) str += $"Zeile {i}"; sw.Stop(); Console.WriteLine($"Zeichenketten anhängen: {sw.ElapsedMilliseconds} ms"); sw.Reset(); // Test 2 StringBuilder sb = new StringBuilder(); sw.Start(); for (int i = 0; i < 190000; i++) sb.Append( $"Zeile {i}"); sw.Stop(); Console.WriteLine($"StringBuilder nutzen: {sw.ElapsedMilliseconds} ms"); } } } }
Hier mein Messergebnis:
Zeichenketten anhängen: 156504 ms
StringBuilder nutzen: 43 ms
--
Viele Grüsse
Peter Fleischer (ehem. MVP für Developer Technologies)
Meine Homepage mit Tipps und Tricks- Bearbeitet Peter Fleischer Mittwoch, 13. Februar 2019 03:53
-
Danke für eure Antworten, das klärt es zumindest ein wenig für mich.
Was den Code angeht, muss ich mich entschuldigen. Beim Schreiben dieser Frage habe ich nur die erste Zeile (Kopfzeile) der Zieldatei im Fokus gehabt, diese setzte ich wie gesagt mit File.WriteAllText.
Die Datensätze selber allerdings mit File.AppendAllText, also ich hänge eigentlich immer nur die aktuelle Zeile an die Datei an. Die Original-Zeile sah so aus:
File.AppendAllText( this.TempFileName, string.Join(";", cols), Encoding.UTF8 );
Das natürlich innerhalb einer Iteration ( while ) über SqlDataReader.Read().
Aber für mein Verständnis füge ich doch immer nur eine Zeile an, oder zieht sich File.AppendAllText den gesamten Inhalt temporär, setzt die Daten ran und speichert es wieder? Das würde es natürlich erklären.
Aber ist das so?
Danke
-
Hallo Arne,
warum unterscheidest Du zwischen der ersten und den restlichen Zeilen und wendest mal WriteAllText und mal AppendAllText an? Das macht eigentlich keinen Sinn. AppendAllText erzeugt die Datei auch, wenn sie noch nicht existiert, also kannst Du auch gleich überall AppendAllText nehmen.
Zu deiner Frage der Vorgehensweise von AppendAllText: Genau das passiert dabei.
MSDN Doku - File.AppendAllText Methode
Öffnet eine Datei, fügt die angegebene Zeichenfolge an die Datei an und schließt dann die Datei.Wenn Du aber x Zeilen schreiben willst, macht es eigentlich keinen Sinn, das einzeln zu machen. Sammel dir die Inhalte daher über einen StringBuilder in einen String und schreib den dann weg.
Der Unterschied zu StreamWriter.WriteLine ist u.a. dass File.AppendAllText, WriteAllText und einige andere Methoden in der Richtung die Datei direkt schreiben. StreamWriter.WriteLine schreibt erst auf Anforderung, bspw. beim Aufruf von StreamWriter.Close (kann auch implizit, bspw. durch using ... passieren)
Daher ist letzteres natürlich erheblich schneller.
Gruß, Stefan
Microsoft MVP - Visual Developer ASP/ASP.NET (2001-2018)
https://www.asp-solutions.de/ - IT Beratung, Softwareentwicklung, Remotesupport- Als Antwort markiert Arne Drews Mittwoch, 13. Februar 2019 07:31