String Concatenation versus StringBuilder
Sorgsamer Umgang mit dem Speicher reduziert Garbage Collection
Nikolai Moesus - Oktober 2017Anti-Pattern Beschreibung
In der Programmiersprache Java wird meist der Plus-Operator für die Verkettung
(Concatenation) von Strings verwendet (bspw. str = str + "more").
Die Zeichenkette einer Instanz der Klasse java.lang.String ist nach der Initialisierung
dieser jedoch konstant und kann nicht verändert werden. Stattdessen wird bei der Ausführung
des Plus-Operators eine neue String-Instanz erzeugt und so die bisherigen String-Instanzen
zur Löschung freigegeben.
Werden String-Verkettungen mit dem Plus-Operator sehr oft aufgerufen, bspw. durch
eine Vielzahl von Microservices-Anrufen oder in einer Programmschleife, so wird kurzzeitig
eine große Menge an String-Instanzen erzeugt und zeitnah wieder zum Löschen freigegeben.
Dieses führt zu einer starken Beanspruchung des Java-Speichers sowie Performanceeinbußen
durch die Speicherbereinigungen des Garbage Collectors.
Für eine speichereffiziente Verkettung von Strings stehen in Java die Klassen
java.lang.StringBuffer und java.lang.StringBuilder zur Verfügung. Beide Klassen ermöglichen
die speichereffiziente Verkettung von Strings mittels der Methoden append und insert.
Software-Code Beispiele
Das erste Code-Beispiel zeigt eine in der Praxis typische Variante für die Verkettung
von Strings. Diese Implementierung führt zur kontinuierlichen Erzeugung von neuen
Objektinstanzen und der zeitnahen Freigabe zur Löschung dieser.
String concatStringsWithPlus () {
final String[] parts = { "these", "are", "separate", "parts", "of", "a",
"string", "that", "get", "connected" };
String str;
for (int i = 0; i < 10; ++i) {
str = str + " " + parts[i];
}
return str;
}
Negatives Code-Beispiel: Ineffiziente Verknüpfung von Strings mittels Plus Operator
Das zweite Code-Beispiel zeigt die Umsetzung mittels der StringBuilder Klasse.
Die append-Methode sorgt für eine speicherschonende Verkettung der Strings.
String concatStringsWithBuilder () {
final String[] parts = { "these", "are", "separate", "parts", "of", "a",
"string", "that", "get", "connected" };
StringBuilder builder = new StringBuilder();
for (int i = 0; i 10; ++i) {
builder.append(" ").append(parts[i]);
}
return builder.toString();
}
Positiv Code-Beispiel: Effiziente Verkettung von Strings mittels StringBuilder
Analyse der Laufzeit
Die Laufzeitanalyse soll uns die Auswirkung der unterschiedlichen Implementierungsvarianten
deutlich machen.
Die folgende Abbildung zeigt zwei nacheinander mittels des Java Performance Servlets
durchgeführte Lastenszenarien. Folgende Einstellungen wurden verwendet:
- 10^5 Durchläufe
- 1 ms Pause
- kein Warm-Up
- Ausführung einer Garbage Collection vor dem Start
Der erste Speicheranstieg in der Abbildung wurde mit dem ersten Code-Beispiel
unter Verwendung des Plus Operator ausgeführt. Der zweite Speicheranstieg mit dem
zweiten Code-Beispiel wurde unter Verwendung des StringBuilders durchgeführt. Der
Unterschied zwischen der Speicherauslastung und der damit performanceintensiveren
Speicherbereinigung ist hier deutlich erkennbar.
Abbildung: Auswirkung auf den Speicherverbrauch bei String Concatenation mittels Plus-Operator
und StringBuilder
Der Softwarecode für das Java Performance Servlet zur Vermessung der Implementierungsvarianten
steht im QMETHODS GitHub zur Verfügung. Nutzen sie es, um Experimente in ihrer eigenen
Umgebung auszuführen. Zur Vermessung haben wir Dynatrace AppMon eingesetzt.
-
Refactoring Empfehlung
Nach unserer Erfahrung ist die Lesbarkeit des Softwarecodes bei der Verwendung
des Plus Operators gegenüber dem StringBuilder für viele Entwickler leichter. Aus
diesem Grund empfiehlt sich der Einsatz des StringBuilders vor allem in hochfrequentierten
Code-Bereichen. In der Regel sind das:
- hochfrequentierte Methoden,
- umfangreiche Programmschleifen (for, while) oder
- rekursive Aufrufe.
Für diese Fälle würden wir ein entsprechendes Refactoring dringend empfehlen!
Zurück zur Anti-Pattern Übersichtseite
Keywords
Java
Plus Operator
Refactoring
StringBuilder
SW Lizenzen