03.08 StringBuffer und StringBuilder
Wie Sie wissen, sind Strings immutable und somit unveränderbar. Dies hat zur Folge, dass bei jeder Verknüpfung von Strings
mit dem + Operator ein neuer String
erzeugt wird, was in den meisten Fällen nicht gerade performant ist. Zwei Alternativen lernen Sie in diesem Kapitel kennen.
Ein Nachteil von Strings
Betrachten wir das oben angesprochene Problem noch einmal in der Praxis. Wenn Sie viele Zeichen einer Zeichenkette in einer Schleife hinzufügen, wird die Geschwindigkeit der Anwendung sehr schnell einbrechen. Führen Sie hierzu folgende Beispielanwendung aus:
package de.jbb; public class Test { public static void main(String[] args) { String str = ""; for (int i = 0; i < 100000; i++) { str += i; } } }
Sie werden feststellen, dass das Programm ungewöhnlich lange läuft. Dies liegt wie bereits erwähnt daran, dass bei jedem Schleifendurchlauf ein neuer String
erzeugt wird.
Die Lösung
Verwenden Sie allerdings anstelle eines Strings
einen StringBuilder
oder einen StringBuffer
, verschwindet das Geschwindigkeits-Problem.
package de.jbb; public class Test { public static void main(String[] args) { StringBuilder str = new StringBuilder(); for (int i = 0; i < 100000; i++) { str.append(i); } } }
Einsatzorte von String, StringBuilder und StringBuffer
Aber sollten Sie deshalb immer einen StringBuilder
oder StringBuffer
anstelle eines Strings
verwenden? Nein! Denn Strings
werden vom Compiler automatisch in StringBuffer
bzw. StringBuilder
umgewandelt. Dies funktioniert aber nicht immer, wie Sie beim ersten Beispiel in diesem Kapitel festgestellt haben. Aus
String str = "Simpler"; str += " String"; String str2 = ""; for (int i = 0; i < 10; i++) { str2 += str2.length(); }
macht der Compiler automatisch bspw. das hier:
String s = "Simpler"; s = (new StringBuilder()).append(s).append(" String").toString(); String s1 = ""; for(int i = 0; i < 10; i++) s1 = (new StringBuilder()).append(s1).append(s1.length()).toString();
Sie sehen, dass der Compiler die Verbindung von Strings, die außerhalb einer Schleife, also nicht dynamisch zusammengebaut werden, soweit akzeptabel löst. In der Schleife wird allerdings grober Unfug erzeugt, der garantiert nicht für die Performance förderlich ist.
Deshalb sollten Sie immer dann auf diese beiden Alternativen ausweichen, wenn es wichtig ist, dass Ihre Zeichenkette dynamisch veränderbar (z. B. in einer Schleife) ist, oder wenn Sie auf deren zusätzlichen Methoden wie z. B. insert
(zusätzlichen Inhalt an eine beliebigen Stelle einfügen), setCharAt
(ein Zeichen an einer bestimmten Stelle ersetzen), oder reverse
(umkehren der Zeichenkette) zugreifen müssen/möchten. Die genauen Methodenaufrufe finden Sie in der Dokumentation zum StringBuilder bzw. StringBuffer. Bei der Entscheidung, ob Sie einen StringBuilder
oder einen StringBuffer
verwenden, sollten Sie beachten, dass ein StringBuffer
im Gegensatz zum StringBuilder
synchronisiert ist (mehr dazu lernen Sie im Kapitel über Threads), und dass es den StringBuilder
erst seit Java 5 gibt (StringBuffer
seit Java 1.0).
StringBuilder und StringBuffer in Kombination mit String
StringBuilder
, StringBuffer
und String
sind nicht miteinander verwandt. Dadurch ist ein Vergleich, wie Sie ihn aus Kapitel 03.02 Strings vergleichen kennen, nicht mehr möglich. Wenn Sie StringBuilder
und StringBuffer
miteinander oder mit einem gewöhnlichen String
vergleichen wollen, müssen Sie diese vorher in einen String
mit der toString
-Methode umwandeln.
StringBuilder build = new StringBuilder("123"); String str = "123"; StringBuffer buff = new StringBuffer("123"); System.out.println(build.equals(buff)); // false System.out.println(buff.equals(str)); // false System.out.println(buff.toString().equals(build.toString())); // true System.out.println(str.equals(buff.toString())); // true
Dies gilt übrigens auch für scheinbar gleiche Instanzen selben Typs:
StringBuffer buffer = new StringBuffer("123"); StringBuffer buffer2 = new StringBuffer("123"); System.out.println(buffer.equals(buffer2)); // false StringBuilder builder = new StringBuilder("123"); StringBuilder builder2 = new StringBuilder("123"); System.out.println(builder.equals(builder2)); // false
Über die String
-Methode contentEquals
können Sie aber zumindest den Inhalt eines Strings
direkt mit dem eines StringBuilders
oder StringBuffers
vergleichen. Wenn Sie einen StringBuffer
mit einem StringBuilder
vergleichen möchten, müssen Sie trotzdem noch mindestens einen von beiden in einen gewöhnlichen String
umwandeln.
StringBuilder build = new StringBuilder("123"); String str = "123"; StringBuffer buff = new StringBuffer("123"); System.out.println(str.contentEquals(buff)); // true System.out.println(build.toString().contentEquals(buff)); // true
Hallo,
ein kleiner Verbesserungsvorschlag, ich finde man könnte bei den Vergleichen noch erwähnen, dass wenn man 2 unterschiedliche Instanzen von StringBuilder/StringBuffer vergleicht auch false rauskommt !!obowhl!! gleichen Inhaltes.
Hallo Michael,
vielen Dank für die Anmerkung. Ich werde das Kapitel sofort ergänzen.
Gruß
Stefan
Hallo,
ich hätte da mal eine kleine Frage. Also d.h. außerhalb einer Schleife ist ein StringBuilder egal wieviel Strings ich aneinanderreihe total überflüßig(außer ich benötige die Methoden)?
Ist es nicht so wenn ich den default StringBuilder () benutze er nach 16 zeichen einen neuen String erzeugt und diesen dann einfügen muss. Darum sollte doch vorher abgeschätzt werden wie groß der Builder werden kann , sonst ist der Vorteil geringer?z.B. new StringBuilder(255);
Ich freu mich auf Ihre Antwort :)!!!
Gruß Marc
Hallo Marc,
völlig egal ist es nicht. Wie am obigen Beispiel gesehen werden kann, würde der Compiler aus dem hier:
das hier machen
Noch performanter wäre es natürlich, wenn man von Anfang an das hier schreiben würde
Aber bei so etwas würde ich die Lesbarkeit (die bei der Verwendung von
String
deutlich höher sein sollte) über den minimalen Performancevorteil stellen.Um die zweite Frage zu beantworte, werfen wir am Besten einen Blick in den Java Sourcecode. Was passiert, wenn ein weiterer
String
an denStringBuilder
viaappend
angehängt wird? Die Lösung befindet sich im Sourcecode der KlasseAbstractStringBuilder
von welcher derStringBuilder
abgeleitet ist.Für die Frage ist nur die Zeile
relevant. Was paassiert, wenn die neue Größe des
StringBuilders
die Alte übersteigt?Die Kapazität wird um das Doppelte erhöht. Wird der Wertebereich des
Integers
überschritten, ist die neue Kapazität gleich der maximalen Größe einesIntegers
. Außerdem wird noch überprüft, ob die Verdoppelung der Kapazität für die neue Größe ausreicht. Ist dies nicht der Fall, wird die aktuelle Größe gleich der neuen Größe gesetzt. Abschließend wird das alte Array, welches den Inhalt desStringBuilders
beinhaltet, in ein neues, an die neue Größe angepasstes, Array kopiert.Der "+16"-Mythos resultiert daraus, dass beim Anlegen eines neuen
StringBuilders
automatisch die Kapazität auf "Länge des Initial-Strings
" + 16 gesetzt wird.Dennoch ist es optimal, die ungefähre Kapazität des
StringBuilders
im Voraus abzuschätzen - sofern möglich.Ich hoffe ich konnte helfen. Ansonsten einfach noch einmal nachfragen.
Gruß
Stefan
Vielen Dank für die schnelle Antwort.
Der Joshua Bloch schreibt, dass man den SB verwenden soll sobald mehr als 4 Strings verknüpft werden.
Zu der Kapazität noch eine Frage, falls man die Größe nicht einschätzen kann. Ist es besser einen „leeren“ StringBuilder anzulegen oder einen vielleicht zu großen z.B. 255???
Hallo Marc,
aus Sicht der Performance ist es besser einen „zu großen“ StringBuilder anzulegen, da später kein aufwendiges Arrays.copyOf() statt finden muss. Aus Sicht der Ressourcen belegt ein StringBuilder dann aber auch sofort ein char Array der entsprechenden Initialgröße (hier 255). Dies ist aber vernachlässigbar.
Im Code zu AbstractStringBuilder kann man sich den Konstruktor auch anschauen:
Gruß
Sebastian