The string splicing methods discussed in this article are the following four: "+" sign, StringBuilder, StringJoiner, String#join, comparative analysis and discussion of best practices.
in conclusion
The following content is relatively boring, so let's talk about the conclusion first:
- The string splicing methods discussed in this article are as follows: "+" sign, StringBuilder, StringJoiner, String#join
- In a simple string concatenation scenario "such as: "a" + "b" + "c"", the performance of the above four methods has no significant difference.
- In the scenario of cyclic string splicing, using the "+" sign has the lowest performance, and there is no significant difference in performance between the other three methods. However, according to the verification results, it can be superficially found that the StringBuilder with the specified initial capacity is the most efficient. Of course, not only performance is considered, but also the efficiency of garbage collection to avoid OOM.
- At the end of this article, a comparison of StringBuffer is added. In the scenario where there is no competition for shared resources, the performance of StringBuffer does not deteriorate significantly.
Best Practices
- Alibaba Java Development Manual - Log Protocol "5" can be optimized: the use of placeholders is not readable and convenient, and Lambda can be considered to delay string splicing, and it is more convenient to use.
- Alibaba Java Development Manual - OOP Protocol "23" can be optimized: StringBuilder must be used when splicing loops; when splicing a large number of large-capacity strings, use StringBuilder to specify the initial capacity as much as possible.
- Simple string concatenation can be done in any way. It is recommended to use the "+" sign to concatenate directly, for the best readability.
- Try to use the features directly provided by JDK, such as "+" splicing strings, Synchronized keywords, etc., because the compiler + JVM will continue to optimize this, and JDK upgrades can get greater benefits. Unless there is a clear reason to implement something similar on your own.
- In scenarios where thread safety needs to be considered, you can consider using StringBuffer for string concatenation, but generally there is no such requirement, so StringBuffer should not be used to avoid increasing complexity.
Analysis process
surroundings
- System: windows 10 21H1
- JDK: OpenJDK 1.8.0_302
- Sample code for analysis:
@Slf4j
public class StringConcat {
@SneakyThrows
public static void main(String[] args) {
log.info("java虚拟机预热开始");
String[] strs = new String[6000000];
for (int i = 0; i < strs.length; i++) {
strs[i] = id();
}
loopStringJoiner(strs);
loopStringJoin(strs);
loopStringBuilder(strs);
log.info("java虚拟机预热结束");
Thread.sleep(1000);
log.info("开始测试:");
Thread.sleep(1000);
Stopwatch stopwatchLoopPlus = Stopwatch.createStarted();
// loopPlus(strs);
log.info("loop-plus: " + stopwatchLoopPlus.elapsed(TimeUnit.MILLISECONDS));
Thread.sleep(1000);
Stopwatch stopwatchLoopStringBuilderCapacity = Stopwatch.createStarted();
loopStringBuilderCapacity(strs);
log.info("loop-stringBuilderCapacity: " + stopwatchLoopStringBuilderCapacity.elapsed(TimeUnit.MILLISECONDS));
Thread.sleep(1000);
Stopwatch stopwatchLoopStringBuilder = Stopwatch.createStarted();
loopStringBuilder(strs);
log.info("loop-stringBuilder: " + stopwatchLoopStringBuilder.elapsed(TimeUnit.MILLISECONDS));
Thread.sleep(1000);
Stopwatch stopwatchLoopJoin = Stopwatch.createStarted();
loopStringJoin(strs);
log.info("loop-String.join: " + stopwatchLoopJoin.elapsed(TimeUnit.MILLISECONDS));
Thread.sleep(1000);
Stopwatch stopwatchLoopStringJoiner = Stopwatch.createStarted();
loopStringJoiner(strs);
log.info("loop-stringJoiner: " + stopwatchLoopStringJoiner.elapsed(TimeUnit.MILLISECONDS));
Thread.sleep(1000);
Stopwatch stopwatchSimplePlus = Stopwatch.createStarted();
for (int i = 0; i < 500000; i++) {
simplePlus(id(), id(), id());
}
log.info("simple-Plus: " + stopwatchSimplePlus.elapsed(TimeUnit.MILLISECONDS));
Thread.sleep(1000);
Stopwatch stopwatchSimpleStringBuilder = Stopwatch.createStarted();
for (int i = 0; i < 500000; i++) {
simpleStringBuilder(id(), id(), id());
}
log.info("simple-StringBuilder: " + stopwatchSimpleStringBuilder.elapsed(TimeUnit.MILLISECONDS));
Thread.sleep(1000);
Stopwatch stopwatchSimpleStringBuffer = Stopwatch.createStarted();
for (int i = 0; i < 500000; i++) {
simpleStringBuffer(id(), id(), id());
}
log.info("simple-StringBuffer: " + stopwatchSimpleStringBuffer.elapsed(TimeUnit.MILLISECONDS));
}
private static String loopPlus(String[] strs) {
String str = "";
for (String s : strs) {
str = str + "+" + s;
}
return str;
}
private static String loopStringBuilder(String[] strs) {
StringBuilder str = new StringBuilder();
for (String s : strs) {
str.append("+");
str.append(s);
}
return str.toString();
}
private static String loopStringBuilderCapacity(String[] strs) {
StringBuilder str = new StringBuilder(strs[0].length() * strs.length);
for (String s : strs) {
str.append("+");
str.append(s);
}
return str.toString();
}
private static String loopStringJoin(String[] strs) {
StringJoiner joiner = new StringJoiner("+");
for (String str : strs) {
joiner.add(str);
}
return joiner.toString();
}
private static String loopStringJoiner(String[] strs) {
return String.join("+", strs);
}
private static String simplePlus(String a, String b, String c) {
return a + "+" + b + "+" + c;
}
private static String simpleStringBuilder(String a, String b, String c) {
StringBuilder builder = new StringBuilder();
builder.append(a);
builder.append("+");
builder.append(b);
builder.append("+");
builder.append(c);
return builder.toString();
}
private static String simpleStringBuffer(String a, String b, String c) {
StringBuffer buffer = new StringBuffer();
buffer.append(a);
buffer.append("+");
buffer.append(b);
buffer.append("+");
buffer.append(c);
return buffer.toString();
}
private static String id() {
return UUID.randomUUID().toString();
}
}
Results and Summary
- java虚拟机预热开始
- java虚拟机预热结束
- 开始测试:
- loop-plus: 执行超时
- loop-stringBuilderCapacity: 285
- loop-stringBuilder: 1968
- loop-String.join: 1313
- loop-stringJoiner: 1238
- simple-Plus: 812
- simple-StringBuilder: 840
- simple-StringBuffer: 857
- After many tests, it can be found that in the scenario of cyclic splicing of strings, the direct use of the "+" sign has the lowest performance, the StringBuilder with initial capacity has the highest performance, and the performance of other methods is not much different.
- After many tests, it can be found that in the scenario of simple string splicing, the performance difference between using the "+" sign, StringBuilder, and StringBuffer is about 5%, which can be understood as a test error, and the performance of the three methods can be considered to be consistent.
Code and result analysis
1. StringBuilder vs StringBuffer
In the scenario where there is no competition for shared resources, the JVM will use biased locks and other methods to optimize, and even eliminate locks. There is no significant difference in performance whether the Synchronized keyword is used or not.
2. Bytecode Analysis
Comparing the bytecodes of the above two methods #simplePlus and #simpleStringBuilder, it is obvious that the execution content of the two methods is basically the same, but the processing flow is shorter when the "+" sign is directly used. It can be seen that the compiler has carried out in-depth optimization. The bytecode would theoretically have higher performance:
// access flags 0xA
private static simplePlus(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
// parameter a
// parameter b
// parameter c
L0
LINENUMBER 125 L0
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 0
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "+"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "+"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 2
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ARETURN
L1
LOCALVARIABLE a Ljava/lang/String; L0 L1 0
LOCALVARIABLE b Ljava/lang/String; L0 L1 1
LOCALVARIABLE c Ljava/lang/String; L0 L1 2
MAXSTACK = 2
MAXLOCALS = 3
// access flags 0xA
private static simpleStringBuilder(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
// parameter a
// parameter b
// parameter c
L0
LINENUMBER 129 L0
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ASTORE 3
L1
LINENUMBER 130 L1
ALOAD 3
ALOAD 0
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
POP
L2
LINENUMBER 131 L2
ALOAD 3
LDC "+"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
POP
L3
LINENUMBER 132 L3
ALOAD 3
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
POP
L4
LINENUMBER 133 L4
ALOAD 3
LDC "+"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
POP
L5
LINENUMBER 134 L5
ALOAD 3
ALOAD 2
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
POP
L6
LINENUMBER 135 L6
ALOAD 3
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ARETURN
L7
LOCALVARIABLE a Ljava/lang/String; L0 L7 0
LOCALVARIABLE b Ljava/lang/String; L0 L7 1
LOCALVARIABLE c Ljava/lang/String; L0 L7 2
LOCALVARIABLE builder Ljava/lang/StringBuilder; L1 L7 3
MAXSTACK = 2
MAXLOCALS = 4
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。