Du verwendest einen veralteten Browser. Es ist möglich, dass diese oder andere Websites nicht korrekt angezeigt werden. Du solltest ein Upgrade durchführen oder ein alternativer Browser verwenden.
Hallo,
ich hätte eine Frage. Bei einem meiner Codes habe ich einen String mit dem Wert " " deklariert. Danach wurde mir aber ein String Builder empfohlen. Grundsätzlich habe ich keinen Unterschied entdeckt. Vielleicht kann mir ja jemand anders auf die Sprünge helfen, was sich hier verändert.
java.lang.StringBuilder ist dazu da, um effizient aus mehreren einzelnen hintereinander konkatenierten Strings einen einzelnen String zu bauen, da bei einer "manuellen" String-Konkatenation via z.B. `String c = a+b;` (wobei die Strings `a` und/oder `b` keine Konstanten sind) immer ein neues String-Objekt gebaut wird. Der javac Compiler ist hier allerdings schon sehr intelligent und kann bei vielen Mustern/Verwendungen von String-Konkatenationen diese selbst durch StringBuilder und Aufrufen von dessen append() Methode ersetzen.
Wenn du einfach _nur_ einen String hast mit `String a = " ";` dann bringt dir StringBuilder natürlich nichts. Wie gesagt, ist StringBuilder dazu da, durch mehrere Konkatenationen einen finalen String zu bauen, ohne, dass bei jeder Konkatenation immer wieder ein neues String-Objekt als Zwischenergebnis entsteht. Sowas trifft man z.B. sehr häufig bei toString() Methoden von Klassen an, da hier oft String-Repräsentationen der Instanzfelder gebaut und das Ganze als finaler String zurückgegeben wird.
java.lang.StringBuilder ist dazu da, um effizient aus mehreren einzelnen hintereinander konkatenierten Strings einen einzelnen String zu bauen, da bei einer "manuellen" String-Konkatenation via z.B. `String c = a+b;` (wobei die Strings `a` und/oder `b` keine Konstanten sind) immer ein neues String-Objekt gebaut wird. Der javac Compiler ist hier allerdings schon sehr intelligent und kann bei vielen Mustern/Verwendungen von String-Konkatenationen diese selbst durch StringBuilder und Aufrufen von dessen append() Methode ersetzen.
Wenn du einfach _nur_ einen String hast mit `String a = " ";` dann bringt dir StringBuilder natürlich nichts. Wie gesagt, ist StringBuilder dazu da, durch mehrere Konkatenationen einen finalen String zu bauen, ohne, dass bei jeder Konkatenation immer wieder ein neues String-Objekt als Zwischenergebnis entsteht. Sowas trifft man z.B. sehr häufig bei toString() Methoden von Klassen an, da hier oft String-Repräsentationen der Instanzfelder gebaut und das Ganze als finaler String zurückgegeben wird.
Alles klar, klingt logisch. Also einfach Ressourcensparend und ein direktes Zusammenfügen, ohne immer wieder neue Strings zu erzeugen. Man hat also nur einen Finalen String. Danke!
Ein StringBuilder ist nur dann interessant wenn es um Speicher geht.
Also als Hintergrund, Strings in Java sind unveraenderlich, also egal welche veraendernde Operation auf diesen ausfuehrst, du wirst immer eine neue Instanz bekommen. Zum Beispiel:
Java:
String a = "abcdefgh"; // "abcdefgh"
String b = a.substring(1, 2): // "bc"
String c = a.substring(1, 2): // "bc"
b == c; // false
b.equals(c); // true
Es gibt dann noch interning (String-Cache) und interne Verwaltung, die spielt hier jetzt aber weniger eine Rolle. Mit diesem Wissen das wir immer eine neue Instanz bekommen, sehen wir uns mal konkatenieren an:
Java:
String a = "abc";
String b = "def";
String c = a + b;
In Speicher ausgedrueckt bedeutet das:
Java:
String a = "abc"; // 3 Byte; 3 Byte Gesamt
String b = "def"; // 3 Byte; 6 Byte Gesamt
String c = a + b; // 6 Byte; 12 Byte Gesamt
Da wir nur unveraenderliche Instanzen haben, bedeutet dass das wir zu irgendeinem Zeitpunkt den doppelten Speicherbedarf des Ziel-Strings haben. Mit kurzen Strings ist das egal, aber mit laengeren wird es interessant:
Java:
String a = "riesiger String"; // 250MB
String b = "zweiter riesiger String"; // 250MB
String c = a + b; // Gesamter Speicherbedarf fuer diese Operation: 1000MB
Jede String-Konkatenierung besteht also aus diesen Operationen:
1. Ziel-Speicher fuer "c" allokieren
2. Variable "a" in "c" kopieren
3. Variable "b" in "c" kopieren
Effektiv koennte man es so ausdruecken:
Java:
// Java Code
String c = a + b;
// Under the hood
byte[] bufferC = new byte[a.length() + b.length()];
System.arrayCopy(bufferC, 0, a.buffer, 0, a.length());
System.arrayCopy(bufferC, a.length(), b.buffer, 0, b.length());
String c = new String(bufferC);
Wenn wir das nun in einer Schleife machen, haben wir sehr viele "unnoetige" Speicher-Allokierungen und Kopiervorgaenge. Tatsaechlich sind Allokierungen in Java spot billig (wenn dieser bereits vom Betriebssytem geholt wurde und in der JVM verfuegbar ist), Speicher kopieren ist aber immer noch Speicher kopieren.
Java:
String result = "";
for (int counter = 0; counter < 1_000_000; counter++) {
result = result + "a";
}
Bei dieser Operation haben wir also eine Million neue Speicher Allokierungen, und zwei Millionen Kopiervorgaenge mit wachsenden Speichergroeszen ("result" muss ja in die neue String-Instanz kopiert werden und es wird jedes Mal groeszer).
Der StringBuilder hingegen hat diese Probleme nicht, da dieser nicht unveraenderlich ist, und sich stattdessen intern einen Buffer haelt:
Java:
StringBuilder result = new StringBuilder();
for (int counter = 0; counter < 1_000_000; counter++) {
result.append("a");
}
Dies hat nur einen Bruchteil der vorherigen Allokierungen und der Haelfte an Kopiervorgaengen, da sich der StringBuilder intern einen Buffer haelt, welcher nur waechst wenn es notwendig ist. Die eigentliche Implementierung ist relativ simpel und kann in etwa so ausgedrueckt werden:
Java:
// Java Code
result.append("a");
// Under the hood
int remainingSpaceInBuffer = buffer.length - currentSize;
int requiredSpaceInBuffer = parameter.length();
// Pruefen ob genuegend freier Platz im Buffer ist.
if (remainingSpaceInBuffer < requiredSpaceInBuffer) {
// Wenn nicht, einfach vergroeszern auf das Doppelte.
byte[] newBuffer = new byte[buffer.length * 2];
System.arraycopy(newBuffer, 0, buffer, 0, currentSize);
buffer = newBuffer;
}
// Anhaengen der Werte.
System.arraycopy(buffer, currentSize, parameter, 0, parameter.length);
currentSize = currentSize + parameter.length;
Also der interne Speicher von StringBuilder wird immer auf das doppelte vergroeszert, und dann wird nur noch in diesen Speicher hineinkopiert.
Das gesagt, wo es nichts bringt einen StringBuilder einzusetzen ist, wenn die Anzahl und Groesze der Strings komplett vernachlaessigbar ist. Zum Beispiel wenn du Vor- mit Nachnamen zusammen haengen willst. Oder auch im Code selbst, wenn die Strings alle vor dem kompilieren bekannt sind, bringt es nichts einen StringBuilder einzusetzen. Wo es interessant wird, ist wenn du die Groesze des resultierenden Strings nicht kennst, zum Beispiel bei Schleifen, oder wenn es um wirklich grosze Strings geht und du Speicher sparen musst.
Java:
// Kein StringBuilder hilfreich:
String a = "abc" + "def";
String b = a + "edf";
String c = einKleinerString + einAndererKleinerString;
// Wo StringBuilder hilfreich:
for (String value : values) {
result.append(value);
}
Wie ich am Anfang meinte, es gibt vier Dinge die man hier aber beachten muss. Ebenfalls muss man beachten dass das hier JVM-Interna sind, also Implementierungsdetails der JVM, auf welche man sich nicht verlassen darf.
Erstens, Man kann von Laenge des Strings nicht direkt auf den Speicherverbrauch schlieszen. Zunaechst sind die Strings in der JVM UTF-16, also jedes Zeichen braucht zwei Byte, zum anderen wurde in neueren Versionen der JVM hier eine Optimierung implementiert nach welchem nicht mehr alle Strings UTF-16 verwenden, sondern wenn moeglich nur noch ASCII (glaube ich), auf jeden Fall 1-Byte pro Zeichen.
Zweitens, in der selben Schiene, es gibt einige Operationen welche nicht direkt den Speicher erhoehen, zum Beispiel substring liefert zwar eine neue String Instanz, aber das interne byte/char-Array des Strings wird zwischen beiden Instanzen geteilt. Also die Speicherlast steigt dadurch nur minimal.
Drittens, es gibt den "interning" Mechanismus, einen JVM-internen String-Cache. Zum Beispiel alle Strings welche beim kompilieren bekannt sind, sind automatisch in diesem Cache, also sind die selbe Instanz. Ein Beispiel"
Java:
String a = "abc";
String b = "abc";
a == b; // true
a.equals(b); //true
String a = new String("abc");
String b = new String("abc");
a == b; // false
a.equals(b); //true
String a = "abc";
String b = new String("abc").intern();
a == b; // true
a.equals(b); //true
Mit der String.intern Funktion bekommt man immer eine Instanz des selben Strings aus dem Cache. Damit kann man auch die Speicherlast reduzieren zu einem gewissen Grad in bestimmten Faellen.
Viertens, der Compiler veraendert den Code fuer Optimierungen, zum Beispiel:
Java:
String a = "abc" + "def" + "ghi" + "jkl";
Wird in vielen Faellen vom Compiler umgewandelt in:
Java:
String a = new StringBuilder().append("abc").append("def").append("ghi").append("jkl").toString();
Das kannst du mit einem Decompiler ausprobieren und nachschauen (aber passt auf, auch Decompiler optimieren den Code wieder den sie ausspucken). Auch das alles ist natuerlich ein Implementierungsdetail des Compilers.
String x = new String("bob");
x +="2";
String y = "bob2";
x == y => false
das mit dem string als objekt kommt auch noch dazu .. ausserdem ist es fancy mit dem string builder zu hanteiren als mit + operator... ist auch besser lesbar
Ein StringBuilder ist nur dann interessant wenn es um Speicher geht.
Also als Hintergrund, Strings in Java sind unveraenderlich, also egal welche veraendernde Operation auf diesen ausfuehrst, du wirst immer eine neue Instanz bekommen. Zum Beispiel:
Java:
String a = "abcdefgh"; // "abcdefgh"
String b = a.substring(1, 2): // "bc"
String c = a.substring(1, 2): // "bc"
b == c; // false
b.equals(c); // true
Es gibt dann noch interning (String-Cache) und interne Verwaltung, die spielt hier jetzt aber weniger eine Rolle. Mit diesem Wissen das wir immer eine neue Instanz bekommen, sehen wir uns mal konkatenieren an:
Java:
String a = "abc";
String b = "def";
String c = a + b;
In Speicher ausgedrueckt bedeutet das:
Java:
String a = "abc"; // 3 Byte; 3 Byte Gesamt
String b = "def"; // 3 Byte; 6 Byte Gesamt
String c = a + b; // 6 Byte; 12 Byte Gesamt
Da wir nur unveraenderliche Instanzen haben, bedeutet dass das wir zu irgendeinem Zeitpunkt den doppelten Speicherbedarf des Ziel-Strings haben. Mit kurzen Strings ist das egal, aber mit laengeren wird es interessant:
Java:
String a = "riesiger String"; // 250MB
String b = "zweiter riesiger String"; // 250MB
String c = a + b; // Gesamter Speicherbedarf fuer diese Operation: 1000MB
Jede String-Konkatenierung besteht also aus diesen Operationen:
1. Ziel-Speicher fuer "c" allokieren
2. Variable "a" in "c" kopieren
3. Variable "b" in "c" kopieren
Effektiv koennte man es so ausdruecken:
Java:
// Java Code
String c = a + b;
// Under the hood
byte[] bufferC = new byte[a.length() + b.length()];
System.arrayCopy(bufferC, 0, a.buffer, 0, a.length());
System.arrayCopy(bufferC, a.length(), b.buffer, 0, b.length());
String c = new String(bufferC);
Wenn wir das nun in einer Schleife machen, haben wir sehr viele "unnoetige" Speicher-Allokierungen und Kopiervorgaenge. Tatsaechlich sind Allokierungen in Java spot billig (wenn dieser bereits vom Betriebssytem geholt wurde und in der JVM verfuegbar ist), Speicher kopieren ist aber immer noch Speicher kopieren.
Java:
String result = "";
for (int counter = 0; counter < 1_000_000; counter++) {
result = result + "a";
}
Bei dieser Operation haben wir also eine Million neue Speicher Allokierungen, und zwei Millionen Kopiervorgaenge mit wachsenden Speichergroeszen ("result" muss ja in die neue String-Instanz kopiert werden und es wird jedes Mal groeszer).
Der StringBuilder hingegen hat diese Probleme nicht, da dieser nicht unveraenderlich ist, und sich stattdessen intern einen Buffer haelt:
Java:
StringBuilder result = new StringBuilder();
for (int counter = 0; counter < 1_000_000; counter++) {
result.append("a");
}
Dies hat nur einen Bruchteil der vorherigen Allokierungen und der Haelfte an Kopiervorgaengen, da sich der StringBuilder intern einen Buffer haelt, welcher nur waechst wenn es notwendig ist. Die eigentliche Implementierung ist relativ simpel und kann in etwa so ausgedrueckt werden:
Java:
// Java Code
result.append("a");
// Under the hood
int remainingSpaceInBuffer = buffer.length - currentSize;
int requiredSpaceInBuffer = parameter.length();
// Pruefen ob genuegend freier Platz im Buffer ist.
if (remainingSpaceInBuffer < requiredSpaceInBuffer) {
// Wenn nicht, einfach vergroeszern auf das Doppelte.
byte[] newBuffer = new byte[buffer.length * 2];
System.arraycopy(newBuffer, 0, buffer, 0, currentSize);
buffer = newBuffer;
}
// Anhaengen der Werte.
System.arraycopy(buffer, currentSize, parameter, 0, parameter.length);
currentSize = currentSize + parameter.length;
Also der interne Speicher von StringBuilder wird immer auf das doppelte vergroeszert, und dann wird nur noch in diesen Speicher hineinkopiert.
Das gesagt, wo es nichts bringt einen StringBuilder einzusetzen ist, wenn die Anzahl und Groesze der Strings komplett vernachlaessigbar ist. Zum Beispiel wenn du Vor- mit Nachnamen zusammen haengen willst. Oder auch im Code selbst, wenn die Strings alle vor dem kompilieren bekannt sind, bringt es nichts einen StringBuilder einzusetzen. Wo es interessant wird, ist wenn du die Groesze des resultierenden Strings nicht kennst, zum Beispiel bei Schleifen, oder wenn es um wirklich grosze Strings geht und du Speicher sparen musst.
Java:
// Kein StringBuilder hilfreich:
String a = "abc" + "def";
String b = a + "edf";
String c = einKleinerString + einAndererKleinerString;
// Wo StringBuilder hilfreich:
for (String value : values) {
result.append(value);
}
Wie ich am Anfang meinte, es gibt vier Dinge die man hier aber beachten muss. Ebenfalls muss man beachten dass das hier JVM-Interna sind, also Implementierungsdetails der JVM, auf welche man sich nicht verlassen darf.
Erstens, Man kann von Laenge des Strings nicht direkt auf den Speicherverbrauch schlieszen. Zunaechst sind die Strings in der JVM UTF-16, also jedes Zeichen braucht zwei Byte, zum anderen wurde in neueren Versionen der JVM hier eine Optimierung implementiert nach welchem nicht mehr alle Strings UTF-16 verwenden, sondern wenn moeglich nur noch ASCII (glaube ich), auf jeden Fall 1-Byte pro Zeichen.
Zweitens, in der selben Schiene, es gibt einige Operationen welche nicht direkt den Speicher erhoehen, zum Beispiel substring liefert zwar eine neue String Instanz, aber das interne byte/char-Array des Strings wird zwischen beiden Instanzen geteilt. Also die Speicherlast steigt dadurch nur minimal.
Drittens, es gibt den "interning" Mechanismus, einen JVM-internen String-Cache. Zum Beispiel alle Strings welche beim kompilieren bekannt sind, sind automatisch in diesem Cache, also sind die selbe Instanz. Ein Beispiel"
Java:
String a = "abc";
String b = "abc";
a == b; // true
a.equals(b); //true
String a = new String("abc");
String b = new String("abc");
a == b; // false
a.equals(b); //true
String a = "abc";
String b = new String("abc").intern();
a == b; // true
a.equals(b); //true
Mit der String.intern Funktion bekommt man immer eine Instanz des selben Strings aus dem Cache. Damit kann man auch die Speicherlast reduzieren zu einem gewissen Grad in bestimmten Faellen.
Viertens, der Compiler veraendert den Code fuer Optimierungen, zum Beispiel:
Java:
String a = "abc" + "def" + "ghi" + "jkl";
Wird in vielen Faellen vom Compiler umgewandelt in:
Java:
String a = new StringBuilder().append("abc").append("def").append("ghi").append("jkl").toString();
Das kannst du mit einem Decompiler ausprobieren und nachschauen (aber passt auf, auch Decompiler optimieren den Code wieder den sie ausspucken). Auch das alles ist natuerlich ein Implementierungsdetail des Compilers.
Das ist ziemlich viel auf einemal, da ich gerade erst in der 3 Woche bei Java bin und ich mich noch garnicht so mit der Materie auseinenader gesetzt habe. Dennoch klingt vieles ziemlich logisch. Das mit der Speicherlast und wie man etwas reduzieren kann, damit kenne ich mich noch garnicht aus. Trotzdem vielen Dank
soweit ich weis werden strings in java in eine HashMap gespeichert intern
diese hashmap werte sind aber unveränderlich
wenn du zwei gleiche strings baust mit x = "..." deuten sie auf den gleichen hashmap eintrag und ergeben true beim vergleich aber sobald du x = new String... hast hast du objekte und die sind wieder ganz anders... usw
Nur ein Teil der Strings, insbesondere Literale, werden "interned", landen also im String Pool. Wird ein String mit Hilfe des String-Konstruktors erzeugt, kommt der String nicht aus dem Pool. Man kann aber einen String per String#intern() in den Pool legen, wenn man möchte.
Es gilt damit folgendes für Referenzvergleiche:
Java:
"X" == "X"
"X" != new String("X")
new String("X") != new String("X")
"X" == new String("X").intern()
Ich bin überzeugter Anhänger von StringBuildern, vor allem in Verbindung mit Formattern. Damit werden vor allem meine Logtexte zusammengbaut ( es gibt jeweils immer eine größere Menge von zu formatierenden Werten und Informationen).
Klar haben wir mittlerweile genügend Rechenleistung, um bei jeder Operation einen neuen String erstellen zu lassen, aber ich find's unterm Strich angenehmer, wirkt ohne viel Aufwand auch gleich besser gekapselt vom Rest.