1. Overview
In this short article, we’re going to look at similarities and differences between StringBuilder and StringBuffer in Java.
Simply put, StringBuilder was introduced in Java 1.5 as a replacement for StringBuffer.
2. Similarities
Both StringBuilder and StringBuffer create objects that hold a mutable sequence of characters. Let’s see how this works, and how it compares to an immutable String class:
String immutable = "abc";
immutable = immutable + "def";
Even though it may look like that we’re modifying the same object by appending “def”, we are creating a new one because String instances can’t be modified.
When using either StringBuffer or StringBuilder, we can use the append() method:
StringBuffer sb = new StringBuffer("abc");
sb.append("def");
In this case, there was no new object created. We have called the append() method on sb instance and modified its content. StringBuffer and StringBuilder are mutable objects.
3. Differences
StringBuffer is synchronized and therefore thread-safe. StringBuilder is compatible with StringBuffer API but with no guarantee of synchronization.
Because it’s not a thread-safe implementation, it is faster and it is recommended to use it in places where there’s no need for thread safety.
3.1. Performance
In small iterations, the performance difference is insignificant. Let’s do a quick micro-benchmark with JMH:
@State(Scope.Benchmark)
public static class MyState {
int iterations = 1000;
String initial = "abc";
String suffix = "def";
}
@Benchmark
public StringBuffer benchmarkStringBuffer(MyState state) {
StringBuffer stringBuffer = new StringBuffer(state.initial);
for (int i = 0; i < state.iterations; i++) {
stringBuffer.append(state.suffix);
}
return stringBuffer;
}
@Benchmark
public StringBuilder benchmarkStringBuilder(MyState state) {
StringBuilder stringBuilder = new StringBuilder(state.initial);
for (int i = 0; i < state.iterations; i++) {
stringBuilder.append(state.suffix);
}
return stringBuilder;
}
We have used the default Throughput mode – i.e. operations per unit of time (higher score is better), which gives:
Benchmark Mode Cnt Score Error Units
StringBufferStringBuilder.benchmarkStringBuffer thrpt 200 86169.834 ± 972.477 ops/s
StringBufferStringBuilder.benchmarkStringBuilder thrpt 200 91076.952 ± 2818.028 ops/s
If we increase the number of iterations from 1k to 1m then we get:
Benchmark Mode Cnt Score Error Units
StringBufferStringBuilder.benchmarkStringBuffer thrpt 200 77.178 ± 0.898 ops/s
StringBufferStringBuilder.benchmarkStringBuilder thrpt 200 85.769 ± 1.966 ops/s
However, let’s bear in mind that this is a micro-benchmark, which may or may not have a real impact on the actual, real-world performance of an application.
4. Conclusions
Simply put, the StringBuffer is a thread-safe implementation and therefore slower than the StringBuilder.
In single-threaded programs, we can take of the StringBuilder. Yet, the performance gain of StringBuilder over StringBuffer may be too small to justify replacing it everywhere. It’s always a good idea to profile the application and understand its runtime performance characteristics before doing any kind of work to replace one implementation with another.
Finally, as always, the code used during the discussion can be found over on GitHub.