Unterschied zwischen StringBuilder, StringBuffer und String in Java
In der Programmiersprache Java gibt es verschiedene Möglichkeiten, Texte zu manipulieren und zu bearbeiten. Dazu gehören die Klassen StringBuilder, StringBuffer und String. Obwohl sie alle für die Arbeit mit Zeichenketten verwendet werden, gibt es einige wichtige Unterschiede zwischen ihnen. In diesem Artikel werden wir uns genauer mit diesen Unterschieden befassen.
Der wesentliche Unterschied zwischen String, StringBuilder und StringBuffer in Java liegt in ihrer "immutability" (Veränderbarkeit) und Thread-Sicherheit.
String: Strings in Java sind unveränderlich (immutable), d.h. ihr Wert kann nicht geändert werden, sobald sie erstellt wurden. Das macht ihre Verwendung in Multi-Thread-Umgebungen sicher, kann aber in Bezug auf die Speichernutzung weniger effizient sein, da jedes Mal, wenn Sie eine Zeichenkette ändern, ein neues String-Objekt erstellt wird.
StringBuilder: StringBuilder ist eine veränderbare Version von String, die für die Verwendung in Single-Thread-Umgebungen konzipiert ist (also nicht thread-safe!). Er bietet Methoden zum Anhängen, Einfügen oder Löschen von Zeichen aus einer Zeichenkette, so dass Sie Zeichenketten dynamisch erstellen können. StringBuilder ist für Single-Thread-Anwendungen schneller als StringBuffer, da er nicht synchronisiert ist.
StringBuffer: StringBuffer ist ähnlich wie StringBuilder, aber es ist thread-safe, so dass es geeignet für den Einsatz in Multi-Thread-Umgebungen. StringBuffer bietet Methoden zum Anhängen, Einfügen oder Löschen von Zeichen aus einer Zeichenkette, genau wie StringBuilder, aber es ist langsamer als StringBuilder aufgrund des Overheads für die Synchronisation.
Noch ein Beispiel für StringBuilder vs. StringBuffer beim Zugriff aus mehreren Threads:
public class StringBuilderVsStringBuffer {
private static StringBuilder builder = new StringBuilder();
private static StringBuffer buffer = new StringBuffer();
public static void main(String[] args) {
// Hier wird einfach
Runnable runnable = () -> {
for (int i = 0; i < 1000; i++) {
builder.append("s");
buffer.append("s");
}
};
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("StringBuilder Länge: " + builder.length()); // unvorhersehbar
System.out.println("StringBuffer Länge: " + buffer.length()); // immer 2000
}
}
In diesem Beispiel haben wir zwei Threads, die gleichzeitig den Buchstaben "s" an ein StringBuilder- und ein StringBuffer-Objekt anhängen. Da StringBuffer thread-safe ist, können wir erwarten, dass der sich in einer Multi-Thread-Umgebung korrekt verhält (die Länge des StringBuffer-Objekts immer 2000 beträgt - 1000 jeweils von jedem Thread). Beim StringBuilder können die beiden Therads das Objekt gleichzeitig verändern, deswegen können wir am Ende die Länge nicht vorhersagen.
String-Verkettung und Performance
Auch bei String-Konkatenation gibt es wesentliche Unterschiede zwischen StringBuilder, StringBuffer und der String-Verkettung mit dem "+=" Operator:
- StringBuilder: ist für Single-Thread-Umgebungen optimiert ist und deswegen auch bei String-Konkatenation die schnellste der drei Möglichkeiten.
- StringBuffer: Obwohl er StringBuilder ähnelt, ist er synchronisiert, so dass er im Vergleich zu StringBuilder etwas langsamer sein könnte, insbesondere in einer Single-Thread-Umgebung. Bei kleinen Operationen kann der Unterschied jedoch vernachlässigbar sein.
- String-Verkettung (+= Operator): Diese Methode ist weniger effizient für die String-Verkettung, insbesondere wenn sie wiederholt innerhalb einer Schleife durchgeführt wird. Jeder Verkettungsvorgang erstellt ein neues String-Objekt, was zu mehr Speicherzuweisungen und Kopien führt, was im Vergleich zu StringBuilder und StringBuffer zu einer langsameren Leistung führen kann.
Dazu eine kleine Demonstration:
public class StringConcatenationPerformance {
private static final int COUNT = 1000000;
public static void main(String[] args) {
// StringBuilder performance test
long startTime = System.nanoTime();
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < COUNT; i++) {
stringBuilder.append("s");
}
String resultStringBuilder = stringBuilder.toString();
long endTime = System.nanoTime();
double durationStringBuilder = (endTime - startTime) / 1e9;
System.out.println("StringBuilder: " + durationStringBuilder + " sec");
// StringBuffer performance test
startTime = System.nanoTime();
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < COUNT; i++) {
stringBuffer.append("s");
}
String resultStringBuffer = stringBuffer.toString();
endTime = System.nanoTime();
double durationStringBuffer = (endTime - startTime) / 1e9;
System.out.println("StringBuffer: " + durationStringBuffer + " sec");
// String concatenation performance test
startTime = System.nanoTime();
String concatenatedString = "";
for (int i = 0; i < COUNT; i++) {
concatenatedString += "s";
}
endTime = System.nanoTime();
double durationString = (endTime - startTime) / 1e9;
System.out.println("String Concatenation: " + durationString + " sec");
}
}
Auf einem MacBook M1 bekommen wir als Resultat mit COUNT = 1000000
:
StringBuilder: 0.008037541 sec
StringBuffer: 0.011136042 sec
String Concatenation: 21.508547375 sec