StringBuffer vs. StringBuilder performance comparison
Even if the StringBuilder was around for a while now, many people still use StringBuffer in single threaded applications. The main difference is stated in the StringBuffer comments:
“As of release JDK 5, this class has been supplemented with an equivalent class designed for use by a single thread, {StringBuilder}. The StringBuilder class should generally be used in preference to this one, as it supports all of the same operations but it is faster, as it performs no synchronization.”
So StringBuilder is supposed to be faster. But how much faster? Let’s see…
I create a small program to stress test StringBuffer vs. StringBuilder. As a reference I also do straight String concatenation. In a big loop I just add one character to a String/StringBuffer/StringBuilder. I also play with the initial memory allocation for the StringBuilder and StringBuffer cases. Here is the code:
package com.littletutorials.tips;
public class ConcatPerf {
private static final int ITERATIONS = 100000;
private static final int BUFFSIZE = 16;
private void concatStrAdd() {
System.out.print("concatStrAdd -> ");
long startTime = System.currentTimeMillis();
String concat = "";
for (int i = 0; i < ITERATIONS; i++) {
concat += i % 10;
}
long endTime = System.currentTimeMillis();
System.out.print("length: " + concat.length());
System.out.println(" time: " + (endTime - startTime));
}
private void concatStrBuff() {
System.out.print("concatStrBuff -> ");
long startTime = System.currentTimeMillis();
StringBuffer concat = new StringBuffer(BUFFSIZE);
for (int i = 0; i < ITERATIONS; i++) {
concat.append(i % 10);
}
long endTime = System.currentTimeMillis();
System.out.print("length: " + concat.length());
System.out.println(" time: " + (endTime - startTime));
}
private void concatStrBuild() {
System.out.print("concatStrBuild -> ");
long startTime = System.currentTimeMillis();
StringBuilder concat = new StringBuilder(BUFFSIZE);
for (int i = 0; i < ITERATIONS; i++) {
concat.append(i % 10);
}
long endTime = System.currentTimeMillis();
System.out.print("length: " + concat.length());
System.out.println(" time: " + (endTime - startTime));
}
public static void main(String[] args) {
ConcatPerf st = new ConcatPerf();
System.out.println("Iterations: " + ITERATIONS);
System.out.println("Buffer : " + BUFFSIZE);
st.concatStrBuff();
st.concatStrBuild();
st.concatStrAdd();
}
}
This example allows me to play with the number of iterations, the initial buffer size (not for String concatenation) and which tests I run. Nothing smart, just commenting what I don’t want to run.
The numbers are not really important but the percentage difference is.
The first run with all three tests and the default capacity:
Iterations: 100000
Capacity : 16
concatStrBuff -> length: 100000 time: 16
concatStrBuild -> length: 100000 time: 15
concatStrAdd -> length: 100000 time: 10437
This makes it clear I should never use plain String concatenation in big loops. Of course if you just concatenate a few strings once in a while this doesn’t matter. The explanation is clear from the bytecode generated for the loop in the concatStrAdd() method (fragment):
L3
LINENUMBER 15 L3
ICONST_0
ISTORE 4
L4
GOTO L5
L6
LINENUMBER 17 L6
FRAME APPEND [J java/lang/String I]
NEW java/lang/StringBuilder
DUP
ALOAD 3
INVOKESTATIC java/lang/String.valueOf(Ljava/lang/Object;)Ljava/lang/String;
INVOKESPECIAL java/lang/StringBuilder.<init>(Ljava/lang/String;)V
ILOAD 4
BIPUSH 10
IREM
INVOKEVIRTUAL java/lang/StringBuilder.append(I)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;
ASTORE 3
L7
LINENUMBER 15 L7
IINC 4 1
L5
FRAME SAME
ILOAD 4
LDC 100000000
IF_ICMPLT L6
L8
The compiler uses a StringBuilder to concatenate Strings but unfortunately it creates one instance of it at every iteration. Compare it with the code generated for the loop in the concatStrBuild() method (fragment):
L3
LINENUMBER 43 L3
ICONST_0
ISTORE 4
L4
GOTO L5
L6
LINENUMBER 45 L6
FRAME APPEND [J java/lang/StringBuilder I]
ALOAD 3
ILOAD 4
BIPUSH 10
IREM
INVOKEVIRTUAL java/lang/StringBuilder.append(I)Ljava/lang/StringBuilder;
POP
L7
LINENUMBER 43 L7
IINC 4 1
L5
FRAME SAME
ILOAD 4
LDC 100000000
IF_ICMPLT L6
L8
Increasing the initial capacity for StringBuffer and StringBuilder doesn’t make much difference. Clearly with only 100,000 iterations the numbers for StringBuffer and StringBuilder are just noise.
Iterations: 100000
Capacity : 100000
concatStrBuff -> length: 100000 time: 15
concatStrBuild -> length: 100000 time: 16
concatStrAdd -> length: 100000 time: 10594
Let’s crank it up… but without the String concatenation test because it will never finish.
Iterations: 100000000
Capacity : 16
concatStrBuff -> length: 100000000 time: 15142
concatStrBuild -> length: 100000000 time: 10891
Now it is pretty clear StringBuilder is much faster because it avoids synchronization.
Iterations: 100000000
Capacity : 100000000
concatStrBuff -> length: 100000000 time: 14220
concatStrBuild -> length: 100000000 time: 10611
Allocating all the memory at once does make some difference but nothing really spectacular. But if we look at how the memory allocation peaks we get some more food for thought:

Bump 1: StringBuffer with buffer 100000000
Bump 2: StringBuffer with buffer 16
Bump 3: StringBuilder with buffer 100000000
Bump 4: StringBuilder with buffer 16
So StringBuilder is faster by a good percentage (34% on my machine in this case) but remember that it is not thread safe.





Interesting experiments. Just to be clear, which version of the JDK do your figures relate to (5 or 6)?
Misspelled “lenght”.
Adam, you are the worst kind of *** on the internet. You understood exactly what he was saying but had to point out a simple typo.
Next time stick to *** ***.
@Dan
I used Java 6. I should have pointed this out in the post.
@Adam V.
Thanks, I fixed it. Another proof copy/paste is not good for code reuse
@ Pete M.
Sorry I had to edit your comment. I don’t want to misdirect people searching for hotter topics
I haven’t used Java for a couple of years, but shouldn’t you do a “toString” to actually get the final string? I don’t know if it will actually make any difference, though.
You could even argue that StringBuffer is just plain deprecated. Has anyone ever come across a situation where there was a compelling use case for StringBuffer? Is there ever a genuine need to build up a String across multiple threads? Not saying it’s out of the realm of possibility, It just seems a bit of a weird thing to do.
(Well, to be totally honest, I’ve come across one use for StringBuffer: Matcher.appendReplacement is specified for StringBuffer, not StringBuilder).
I tested the code on my Ubuntu Linux box. The results on OpenJDK 1.6.0-b09 were:
Iterations: 100000
Buffer : 16
concatStrBuff -> length: 100000 time: 109
concatStrBuild -> length: 100000 time: 26
Iterations: 100000
Buffer : 100000
concatStrBuff -> length: 100000 time: 101
concatStrBuild -> length: 100000 time: 26
Iterations: 100000000
Buffer : 100000000
concatStrBuff -> length: 100000000 time: 5183
concatStrBuild -> length: 100000000 time: 1727
Iterations: 100000000
Buffer : 16
concatStrBuff -> length: 100000000 time: 6217
concatStrBuild -> length: 100000000 time: 2330
It seems that the speed difference was even more significant on my box. The results for a Sun JVM (1.6.0_06-b02) were almost the same.
You are absolutely correct when saying that people should avoid using synchronized classes where possible. However, (not just) starting with Java 6, sun has invested a lot of effort to reduce synchronization where possible… there’s a quite interesting article regarding this topic over at infoq: http://www.infoq.com/articles/java-threading-optimizations-p1 . Particularly, you might want to check out the -XX+AggressiveOpts flags for your benchmarking.