String-Assemblierung durch StringBuilder vs. StringWriter und PrintWriter

Lesezeit: 10 Minuten

Benutzer-Avatar
CPerkins

Ich bin kürzlich auf eine Redewendung gestoßen, die ich noch nie zuvor gesehen habe: String Assembly by StringWriter und PrintWriter. Ich meine, ich weiß, wie man sie benutzt, aber ich habe sie immer benutzt StringBuilder. Gibt es einen konkreten Grund, das eine dem anderen vorzuziehen? Das StringBuilder Methode erscheint mir viel natürlicher, aber ist es nur Stil?

Ich habe mir hier mehrere Fragen angesehen (einschließlich dieser, die am nächsten kommt: StringWriter oder StringBuilder ), aber keine, bei der sich die Antworten tatsächlich mit der Frage befassen, ob es einen Grund gibt, eine der anderen für die einfache Zeichenfolgenmontage zu bevorzugen.

Dies ist die Redewendung, die ich viele Male gesehen und verwendet habe: String-Assemblierung von StringBuilder:

public static String newline = System.getProperty("line.separator");
public String viaStringBuilder () {
   StringBuilder builder = new StringBuilder();
   builder.append("first thing").append(newline);  // NOTE: avoid doing builder.append("first thing" + newline);
   builder.append("second thing").append(newline);
   // ... several things
   builder.append("last thing").append(newline);
   return builder.toString();
}

Und das ist das neue Idiom: String Assembly von StringWriter und PrintWriter:

public String viaWriters() {
   StringWriter stringWriter = new StringWriter();
   PrintWriter printWriter = new PrintWriter(stringWriter);
   printWriter.println("first thing");
   printWriter.println("second thing");
   // ... several things
   printWriter.println("last thing");
   printWriter.flush();
   printWriter.close();
   return stringWriter.toString();
}

Bearbeiten Es scheint, dass es keine gibt Beton Grund, eine der anderen vorzuziehen, also habe ich die Antwort akzeptiert, die meinem Verständnis am besten entspricht, und allen anderen Antworten +1 gegeben. Darüber hinaus habe ich eine eigene Antwort gepostet, in der ich die Ergebnisse des von mir durchgeführten Benchmarking als Antwort auf eine der Antworten angegeben habe. Dank an alle.

Nochmals bearbeiten Es stellt sich heraus, dass dort ist ein konkreter Grund, einen (insbesondere den StringBuilder) dem anderen vorzuziehen. Was ich beim ersten Mal vermisst habe, war das Hinzufügen des Zeilenumbruchs. Wenn Sie einen Zeilenumbruch hinzufügen (wie oben als separates Anhängen), ist es etwas schneller – nicht enorm, aber zusammen mit der Klarheit der Absicht ist es definitiv besser. Siehe meine Antwort unten für die verbesserten Timings.

  • Sie sollten nicht verwenden builder.append("first thing" + newline). Das Problem ist, dass es eine zusätzliche String-Zuordnung gibt, die nicht benötigt wird (sie erstellt zuerst “first thing\n” und kopiert dann die String-Daten in den Builder). Mach es stattdessen so: builder.append("first thing").append(newline). Dies kopiert direkt "first thing" zum Bauherrn und dann newline. Natürlich sollte StringBuilder nur verwendet werden, wenn die Anzahl der Dinge nicht konstant ist (z. B. in einer Schleife), javac optimiert bereits "a" + "b" + "c" hinein new StringBuilder().append("a").append("b").append("c").toString().

    – Robinst

    28. Februar 2012 um 14:20 Uhr

  • @robinst Entschuldigung für die sehr langsame Antwort. Ich habe diese Frage gerade erneut besucht und Ihren Kommentar bemerkt. Ausgezeichneter Punkt, und ich habe meinen Test wiederholt. Siehe Änderungen.

    – CPerkins

    11. Januar 2013 um 22:13 Uhr

  • Throwable.printStackTrace() erfordert einen Schriftsteller.

    – Ceving

    25. April 2013 um 17:06 Uhr


  • Ich bin überrascht, dass das OP die Antwort aus “stilistischen” Gründen akzeptiert hat. Alan Moores Erklärung ist mehr als zufriedenstellend. Ich bin tatsächlich auf ein Szenario gestoßen, in dem ich gezwungen war, StringWriter zu verwenden, weil die API (API von Drittanbietern) eine Writer-Instanz erwartet und ich den Inhalt nur in die Zeichenfolge und nicht in die Datei/stdout schreiben wollte. Es spielt wirklich keine Rolle, ob StringWriter intern Stringbuffer/Stringbuilder verwendet, solange der Writer nicht mit mehreren Threads geteilt wird.

    – Srikanth Yaradla

    28. Mai 2013 um 13:03 Uhr

Benutzer-Avatar
Alan Moore

StringWriter verwenden Sie, wenn Sie in eine Zeichenfolge schreiben möchten, aber mit einer API arbeiten, die eine erwartet Writer oder ein Stream. Es ist keine Alternative, es ist ein Kompromiss: Sie verwenden StringWriter nur wenn es sein muss.

  • Danke, und das macht Sinn, aber ich habe festgestellt, dass dies in einer Codebasis weit verbreitet ist, die von einer Reihe sehr kluger Leute geschrieben wurde – tatsächlich scheinen sie im Durchschnitt die klügsten zu sein, mit denen ich je gearbeitet habe. Also suche ich nach Gründen, warum sie es getan haben könnten.

    – CPerkins

    5. Juni 2010 um 16:24 Uhr

  • Dies sollte die akzeptierte Antwort auf diese Frage sein. Ich bin tatsächlich auf ein Szenario gestoßen, in dem ich gezwungen war, StringWriter zu verwenden, weil die API (API von Drittanbietern) eine Writer-Instanz erwartet und ich den Inhalt nur in die Zeichenfolge schreiben wollte, anstatt in die Datei/stdout. @Alan hat es einfach genagelt, als er sagte “Sie verwenden StringWriter, wenn Sie müssen”.

    – Srikanth Yaradla

    28. Mai 2013 um 12:56 Uhr

  • Ich bin auf ein Szenario gestoßen, in dem ich gezwungen war, es zu verwenden StringWriter weil die API von das java.lang.Throwable Klasse hat keine Methoden, die a empfangen StringBuilder als Argument, wenn der Stack-Trace einer Ausnahme ausgegeben werden soll (es gibt nur Methoden, die a akzeptieren PrintStream oder PrintWriter).

    Benutzer2027342

    29. Mai 2013 um 23:08 Uhr


  • Um es klar zu sagen, es ist ein Kompromiss, weil Sie einen StringBuilder verwenden wollten (vorausgesetzt, es sind keine mehreren Threads beteiligt), der sauberer und effizienter gewesen wäre.

    – saktiw

    27. Juli 2017 um 11:55 Uhr

Benutzer-Avatar
Stefan C

Stilistisch ist die StringBuilder Ansatz ist sauberer. Es besteht aus weniger Codezeilen und verwendet eine Klasse, die speziell zum Erstellen von Zeichenfolgen entwickelt wurde.

Die andere Überlegung ist, was effizienter ist. Der richtige Weg, dies zu beantworten, wäre, die Alternativen zu benchmarken, aber (basierend auf dem Quellcode) würde ich es tun erwarten von StringBuilder schneller zu sein.

EIN StringWriter verwendet a StringBuffer (eher als ein StringBuilder) unter der Haube, um die in den “Stream” geschriebenen Zeichen zu halten. Also mit a StringWriter für die Saitenmontage anfallen StringBufferden Synchronisierungsaufwand von , unabhängig davon, ob Ihre Anwendung dies benötigt oder nicht. Aber wenn Ihre Anwendung tut Benötigen Sie die Synchronisation, dann sollten Sie die Verwendung von a in Betracht ziehen StringBuffer direkt.


CPerkins hat ein Benchmarking durchgeführt (siehe seine Antwort) und seine Ergebnisse stimmen mit meiner Intuition überein. Sie sagen, dass der Unterschied zwischen optimal StringBuilder vs StringWriter beträgt ~5%, was für a wahrscheinlich unbedeutend ist typisch Anwendung1. Es ist jedoch schön zu wissen, dass die Kriterien “Stil” und “Leistung” dieselbe Antwort geben!

1 – Während eine typische Anwendung nicht die meiste Zeit damit verbringt, Zeichenfolgen zusammenzustellen, gibt es Ausnahmen. Man sollte jedoch keine Zeit damit verbringen, so etwas nur auf Vermutungen zu optimieren. Profilieren Sie zuerst Ihren Code.

Benutzer-Avatar
CPerkins

Okay, da die Antworten die stilistischen Gründe für die Bevorzugung eines gegenüber dem anderen zu betonen scheinen, habe ich mich entschieden, einen Timing-Test zu erstellen.

Bearbeiten: Nach dem obigen Kommentar von Robinst habe ich jetzt drei Methoden: eine, die das Anhängen im PrintWriter(StringWriter)-Stil ausführt, und zwei, die StringBuilder verwenden: eine mit dem Anhängen des Zeilenumbruchs innerhalb des Anhangs (wie im Original: builder.append(thing + newline);), und der andere fügt einen separaten Anhang hinzu (wie oben: builder.append(thing).append(newline);).

Ich habe mein übliches Benchmarking-Schema befolgt: Rufen Sie die Methoden zunächst mehrmals (in diesem Fall 1000) auf, um dem Optimierer Zeit zum Aufwärmen zu geben, und rufen Sie dann jede Methode viele Male (in diesem Fall 100.000) in abwechselnder Reihenfolge auf, damit sich Änderungen ergeben in der Laufzeitumgebung der Workstation gleichmäßiger verteilt sind, den Test selbst mehrmals durchlaufen und die Ergebnisse mitteln.

Oh, und natürlich ist die Anzahl der erstellten Zeilen und die Länge der Zeilen randomisiert, wird aber für jedes Aufrufpaar gleich gehalten, da ich mögliche Auswirkungen der Puffergröße oder Zeilengröße auf die Ergebnisse vermeiden wollte.

Der Code dafür ist hier: http://pastebin.com/vtZFzuds

Hinweis: Ich habe den Pastebin-Code noch nicht aktualisiert, um den neuen Test widerzuspiegeln.

TL, DR? Die durchschnittlichen Ergebnisse für 100.000 Aufrufe jeder Methode liegen ziemlich nahe beieinander:

  • 0,11908 ms pro Aufruf mit StringBuilder (+ Zeilenumbruch innerhalb der Klammer)
  • 0,10201 ms pro Aufruf mit StringBuilder (Zeilenumbruch als separates Anhängen)
  • 0,10803 ms pro Aufruf mit PrintWriter(StringWriter)

Das ist so nah, dass das Timing für mich fast irrelevant ist, also werde ich die Dinge so machen, wie ich es immer getan habe: StringBuilder (mit separatem Anhang), aus stilistischen Gründen. Während ich in dieser etablierten und ausgereiften Codebasis arbeite, werde ich mich natürlich größtenteils an die bestehenden Konventionen halten, um Überraschungen zu minimieren.

  • Die Ergebnisse sind nicht überraschend. Vorausgesetzt, dass jeder bestimmte Anruf an write(...) an StringWriter führt einfach einen passenden Aufruf aus append(...) auf dem internen StringBuilderdas ist genau die Art von Boilerplate, die das Java JIT optimieren soll.

    – jdmichal

    13. November 2014 um 21:27 Uhr

  • @jdmichal StringWriter verwendet a StringBufferdas ist die synchronized Alternative zum StringBuilder. Das JIT muss also auch unnötige Sperren entfernen – was es heute viel besser kann als früher. Dennoch ist das Problem nicht trivial und die Ähnlichkeit der Leistung ist vielleicht etwas überraschender, als Sie vielleicht denken …

    – Boris die Spinne

    8. April 2016 um 10:52 Uhr

  • Relativ gesehen zeigt Ihr Test StringWriter 6% langsamer als StringBuilder (weil es einen synchronisierten StringBuffer intern und wegen seines eigenen Overheads verwendet), während das schlechte Newline-Anhängen ist 17% langsamer als die korrekte Verwendung von StringBuilder. Zugegeben, der Zeitaufwand ist nicht doppelt so hoch, aber “irrelevant” würde ich nicht sagen, besonders wenn man mit großen Datenmengen umgeht. Hallo, fast 10 Jahre alter Thread 🙂

    – Tobi

    24. Februar 2019 um 9:27 Uhr


Benutzer-Avatar
krock

Schriftsteller
– PrintWriter schreibt in einen Stream. Es macht seltsame Dinge wie das Unterdrücken von IOExceptions. System.out ist einer davon. – StringWriter ist ein Writer, der in einen StringBuffer schreibt (ähnlich wie ByteArrayOutputStream für OutputStreams)

StringBuilder
– und natürlich ist StringBuilder das, was Sie wollen, wenn Sie einfach Strings im Speicher konstruieren möchten.

Fazit

Writer sollen per Design Zeichendaten aus einem Stream herausschieben (normalerweise in eine Datei oder über eine Verbindung), daher scheint es ein kleiner Nebeneffekt zu sein, Writer verwenden zu können, um einfach Strings zusammenzustellen.

StringBuilder (und sein synchronisiertes Geschwister StringBuffer) sind entworfen um Saiten zusammenzusetzen (lesen Sie String Bashing). Schaut man sich die an StringBuilder-API Sie können sehen, dass Sie nicht nur einfaches Anhängen, sondern auch Ersetzen, Umkehren, Einfügen usw. durchführen können. StringBuilder ist Ihr Freund zum Erstellen von Strings.

Ich sehe zwei Vorteile der PrintWriter-Methode: (1) Sie müssen nicht “+ newline” am Ende jeder einzelnen Zeile hinzufügen, was tatsächlich zu kürzerem Code führt, wenn Sie viel zu schreiben haben. (2) Sie müssen System.getProperty() nicht aufrufen; Sie lassen den PrintWriter sich Gedanken darüber machen, wie das Zeilenumbruchzeichen aussehen soll.

  • Gute Gedanken. Ich mache mir keine Sorgen, dass ich anrufen muss System.getProperty(), da es sich um einmalige Kosten handelt, die sich auf Null amortisieren, wenn Sie, wie Sie sagen, viel Code schreiben. Definitiv ein guter Punkt, um zu müssen + newline mit StringBuilderobwohl.

    – CPerkins

    23. Oktober 2010 um 14:27 Uhr


Benutzer-Avatar
omid haghighatgoo

in diesem Link: http://nohack.eingenetzt.com/java/java-stringwriter-vs-stringbuilder
Der Autor zeigt, dass der StringWriter sogar etwas schneller ist als der StringBuilder. Und sagte auch: “Wenn also größere Datenmengen ins Spiel kommen, zeigt der StringWriter deutlich seine Leistungsüberlegenheit gegenüber dem StringBuilder.”

  • Gute Gedanken. Ich mache mir keine Sorgen, dass ich anrufen muss System.getProperty(), da es sich um einmalige Kosten handelt, die sich auf Null amortisieren, wenn Sie, wie Sie sagen, viel Code schreiben. Definitiv ein guter Punkt, um zu müssen + newline mit StringBuilderobwohl.

    – CPerkins

    23. Oktober 2010 um 14:27 Uhr


Benutzer-Avatar
A248

Betrachtet man die Implementierung von StringWriter in JDK 11, verwendet es a StringBuffer unterhalb. StringBuffer ist eine synchronisierte Version von StringBuilder, wie in dieser Frage zu StringBuilder vs. StringBuffer beschrieben

In Anbetracht dessen kann StringBuilder etwas schneller sein. Wenn Sie also leistungsabhängigen Code schreiben, bevorzugen Sie StringBuilder, es sei denn, Sie benötigen eine Synchronisierung.

In meinem Fall musste ich zwischen der Verwendung eines StringBuilder oder eines StringWriter als Appendableund ich brauchte die Synchronisation von StringWriter/StringBuffer nicht.

Zusammenfassen:

  • StringBuilder – nicht synchronisiertes Anhängen im Arbeitsspeicher
  • StringBuffer – synchronisiertes In-Memory-Anhängen
  • StringWriter – umschließt StringBuffer, erweitert auch Writer

1075880cookie-checkString-Assemblierung durch StringBuilder vs. StringWriter und PrintWriter

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy