Hello everyone!
In the previous post, I showed you how to create a JMH project with Gradle. In this article, I will compare the performance of different string concatenation methods in Java.
At the end of that post, I shared a report as follows:
| Benchmark | Mode | Cnt | Score | Error | Units | |
|---|---|---|---|---|---|---|
| StringAppend.bmStringBuffer | thrpt | 5 | 13566863.27 | ± | 427443.554 | ops/s |
| StringAppend.bmStringBufferx0005 | thrpt | 5 | 5529261.499 | ± | 277625.096 | ops/s |
| StringAppend.bmStringBufferx1000 | thrpt | 5 | 36998.492 | ± | 2895.325 | ops/s |
| StringAppend.bmStringBuilder | thrpt | 5 | 13462457.33 | ± | 978240.749 | ops/s |
| StringAppend.bmStringBuilderx0005 | thrpt | 5 | 5466705.447 | ± | 176616.966 | ops/s |
| StringAppend.bmStringBuilderx1000 | thrpt | 5 | 36989.126 | ± | 1716.705 | ops/s |
| StringAppend.bmStringConcat | thrpt | 5 | 27930719.3 | ± | 581249.324 | ops/s |
| StringAppend.bmStringConcatx0005 | thrpt | 5 | 7170529.805 | ± | 383608.303 | ops/s |
| StringAppend.bmStringConcatx1000 | thrpt | 5 | 47104.039 | ± | 2358.121 | ops/s |
| StringAppend.bmStringJoin | thrpt | 5 | 10204201.92 | ± | 463528.75 | ops/s |
| StringAppend.bmStringJoinx0005 | thrpt | 5 | 4322622.068 | ± | 49032.328 | ops/s |
| StringAppend.bmStringJoinx1000 | thrpt | 5 | 30493.885 | ± | 1708.261 | ops/s |
| StringAppend.bmStringPlus | thrpt | 5 | 13689364.1 | ± | 509784.615 | ops/s |
| StringAppend.bmStringPlusx0005 | thrpt | 5 | 5590364.456 | ± | 277476.212 | ops/s |
| StringAppend.bmStringPlusx1000 | thrpt | 5 | 288.086 | ± | 15.175 | ops/s |
This is the result of comparing the performance of different string concatenation approaches in Java.
Now, I will explain why we get these results.
1. String Plus (Addition Operator)
This is the most common way to concatenate strings in Java. For example:
| |
Line 1 creates a String object with the value "Hello", line 2 creates another String with the value "Hello world". However, before creating a new String, Java checks whether that string literal is already in the String Pool.
If it is, Java reuses the string from the pool instead of allocating a new one; otherwise, it creates a new String and adds it to the pool (I will write a dedicated post with more details).
As shown in the benchmark results, this method performs well for x1 and x5 iterations, but is the worst for x1000. This is because with fewer concatenations Java only creates a few objects, but with high iteration counts, repeated object allocations severely degrade performance.
2. String Concat
Compared to String Plus, the performance difference is substantial. Why is there such a big difference? Let’s take a look at the source code:
| |
The code above creates a new String by copying the char arrays of both strings directly. This bypasses the String Pool mechanism and saves significant overhead.
3. String Builder
| |
Above are the two methods invoked when we call the append() method.
As we can see, the principle is similar to concat, except that it modifies an internal buffer and returns AbstractStringBuilder instead of allocating a new String on every append.
4. String Buffer
StringBuffer is the thread-safe version of StringBuilder, so being slower is expected. We can look at the code below to see how thread safety is implemented:
| |
The synchronized keyword ensures that only one thread can execute this method at any given moment, guaranteeing thread safety at the cost of synchronization overhead.
5. String Join
Conclusion
If there are any other string concatenation methods you’d like to see benchmarked, feel free to comment or open a merge request so I can update the post! ;))