Java 中字符串拼接是一个常见操作,而选择合适的拼接方法对于性能有重要影响。
1. 使用 “+” 进行字符串拼接
首先看一个简单的字符串拼接示例:
java
String str1 = "he";
String str2 = "llo";
String str3 = "world";
String str4 = str1 + str2 + str3;
上述代码在 Java 编译后的字节码如下(见下图):
0: ldc #2 // "he"
2: astore_1
3: ldc #3 // "llo"
5: astore_2
6: ldc #4 // "world"
8: astore_3
9: new #5 // class java/lang/StringBuilder
12: dup
13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
16: aload_1
17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: aload_2
21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: aload_3
25: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore_4
32: return
通过上面的字节码可以看出,字符串对象通过“+”拼接的方式实际上是通过StringBuilder
调用append()
方法实现的。拼接完成后,通过toString()
得到一个String
对象。
2. 在循环内使用“+”进行字符串拼接的问题
在循环内使用“+”进行字符串拼接会有性能问题:
java
String[] arr = {"he", "llo", "world"};
String s = "";
for (int i = 0; i < arr.length; i++) {
s += arr[i];
}
System.out.println(s);
对应的字节码如下(见下图):
0: iconst_0
1: istore_1
2: iload_1
3: aload_0
4: arraylength
5: if_icmpge 37
8: new #6 // class java/lang/StringBuilder
11: dup
12: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V
15: aload_2
16: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: aload_0
20: iload_1
21: aaload
22: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
25: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
28: astore_2
29: iinc 1, 1
32: goto 2
35: return
在循环中,StringBuilder
对象是在每次循环时创建的,这会导致创建大量的临时StringBuilder
对象,影响性能。
3. 使用 StringBuilder
进行字符串拼接
更好的做法是直接使用StringBuilder
:
java
String[] arr = {"he", "llo", "world"};
StringBuilder s = new StringBuilder();
for (String value : arr) {
s.append(value);
}
System.out.println(s);
对应的字节码如下(见下图):
0: new #6 // class java/lang/StringBuilder
3: dup
4: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V
7: astore_1
8: aload_0
9: arraylength
10: istore_2
11: iconst_0
12: istore_3
13: iload_3
14: iload_2
15: if_icmpge 37
18: aload_1
19: aload_0
20: iload_3
21: aaload
22: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
25: iinc 3, 1
28: goto 13
31: return
可以看到,StringBuilder
对象是在循环外创建的,并且在整个循环过程中复用同一个StringBuilder
对象,从而避免了创建大量的临时对象。
4. JDK 9 的改进
JDK 9 中,字符串相加“+”的实现进行了优化,改用了动态方法makeConcatWithConstants()
。这样在使用“+”进行字符串拼接时,不再产生大量的临时对象。
java
String s1 = "hello";
String s2 = "world";
String s3 = s1 + s2;
在 JDK 9 中对应的字节码如下:
0: ldc #2 // "hello"
2: astore_1
3: ldc #3 // "world"
5: astore_2
6: aload_1
7: aload_2
8: invokedynamic #4, 0 // Bootstrap methods
13: astore_3
14: return
invokedynamic
指令调用了makeConcatWithConstants
方法,从而避免了创建多余的StringBuilder
对象,提高了性能。
总结
- 单次字符串拼接:使用“+”操作符,因为编译器会优化成
StringBuilder
操作。 - 循环中的字符串拼接:建议使用
StringBuilder
,避免创建大量临时对象。 - JDK 9 及以后:使用“+”进行字符串拼接已经优化,不会有性能问题。