Zum Verketten von Strings gibt es in .NET im Prinzip zwei grundlegende Möglichkeiten: Entweder, ich füge mir meine Wunschzeichenkette ganz simpel mit dem + – Operator zusammen – oder ich mache mir die Mühe, und benutze die extra dafür von Microsoft erschaffene”StringBuilder-Klasse.Warum sollte ich mich aber für Variante zwei entscheiden? Die ist doch – zumindest bei simplen Anwendungsfällen – aufwändiger als die Benutzung des + – Operators!
Das ist nicht ganz falsch – schauen wir uns das doch einmal näher an:
Statt
string test = "def";
test += "ghi";
test = "abc" + test;
return test;
schreibe ich nun
StringBuilder testBuilder = new StringBuilder("def");
testBuilder.Append("ghi");
testBuilder.Insert(0, "abc");
return testBuilder.ToString();
Mehr Codezeilen sind es nicht geworden, höchstens etwas länger sind sie. Und irgendwie auch etwas unübersichtlicher. Warum nun aber StringBuilder statt + – Operator ist immer noch nicht ganz klar. Dafür bauen wir das Beispiel ein bisschen aus und schreiben eine kleine Konsolen-Anwendung. Diese soll einfach in einer Schleife Strings verketten, und zwar einmal mit Hilfe des + – Operators und einmal mit einem StringBuilder. Für beide Versionen soll dabei die Zeit gemessen und die Differenz ausgegeben werden:
static void Main(string[] args)
{
// Anzahl von Schleifendurchläufen (bzw. Verkettungen)
const int NUM_CHARS = 100000;
// Version 1: + - Operator
Console.WriteLine("Version 1: + operator " +
NUM_CHARS.ToString() + " times...");
DateTime version1Start = DateTime.Now;
string version1 = "";
for (int i = 0; i < = NUM_CHARS; i++)
version1 += i.ToString();
TimeSpan version1Duration = DateTime.Now - version1Start;
Console.WriteLine("Version 1 finished. Duration: " +
version1Duration);
Console.WriteLine();
// Version 2: StringBuilder
Console.WriteLine("Version 2: StringBuilder " +
NUM_CHARS.ToString() + " times...");
StringBuilder version2Builder = new StringBuilder();
DateTime version2Start = DateTime.Now;
for (int j = 0; j <= NUM_CHARS; j++)
version2Builder.Append(j);
TimeSpan version2Duration = DateTime.Now - version2Start;
Console.WriteLine("Version 2 finished. Duration: " +
version2Duration);
Console.WriteLine();
// Zeitdifferenz ausgeben
TimeSpan difference = version2Duration - version1Duration;
Console.WriteLine("Difference (Version 2 - Version 1): " +
difference.TotalSeconds);
Console.ReadKey();
}
Führen wir die Anwendung nun aus, so zeigt sich in etwa folgendes Ergebnis:
Die StringBuilder-Version ist bei 100.000 Zeichen gut 135 Sekunden (oder nahezu 100%) schneller als die + - Operator-Version. Oder anders ausgedrückt: Der Anwender muss nicht über zwei Minuten auf das Ergebnis warten, sondern gar nicht! Aber warum ist das so?
Bei Verkettung mit dem + - Operator wird jedes Mal ein neues String-Objekt aus dem Inhalt des "alten" Strings und dem dazu zu fügenden Teil erzeugt und der dafür notwendige Speicher alloziert. Wird der StringBuilder benutzt, so alloziert dieser im Voraus eine gewisse Menge Speicher (die bei Bedarf dynamisch vergrößert wird). Anstatt bei jeder Verkettung nun einen neuen String zu erzeugen wird nur der neue Inhalt an die entsprechende Speicheradresse kopiert. Um das Ergebnis zu erhalten ruft man am Ende StringBuilder.ToString() auf. Auch hier wird kein neuer String erzeugt, sondern lediglich ein String-Objekt zurück gegeben, welches auf den String innerhalb des StringBuilders zeigt.
Sollte man dann nicht lieber immer gleich einen StringBuilder anstelle eines Strings einsetzen? Die Antwort lautet: Nein. Viele Dinge, die ich mit einem String machen kann, beherrscht der StringBuilder nicht. Außerdem wird der Code durch die Benutzung des StringBuilders schwerer lesbar. Zwei Strings mit Hilfe eines StringBuilders zusammen zu fügen ist völlig übertrieben. In Schleifen aber oder anderenorts bei vielen aufeinander folgenden Verkettungen bietet der StringBuilder ungeahnte Performance-Vorteile. Auch, wenn Algorithmen in diesem Fall etwas aufwändiger werden sollten so wird man für den Umbau durch eine deutlich gesteigerte Geschwindigkeit belohnt werden.
