String的不可变性带来的一定效率问题。
public class Concatenation {
public static void main(String[] args) {
String mango="mango";
String s="abc"+mango+"def"+47;
System.out.println(res);
}
}
这种“+”会产生一大堆需要垃圾回收的中间对象。可以通过jdk自带的javap来反编译以上代码,就知道以上代码如何工作的。(为关注有效部分,JVM字节码经过有效简化,下同)
D:\Download>javap -c Concatenation.class
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String mango
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: ldc #5 // String abc
12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: aload_1
16: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc #7 // String def
21: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: bipush 47
26: invokevirtual #8 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
29: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
32: astore_2
33: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
36: aload_2
37: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
40: return
}
这-c标志表示将生成JVM字节码,其中dup和invokevirtual语句相当于Java虚拟机上的汇编语句,可以看出重点是:编译器自动引入了java.lang.StringBuilder类。虽然我们源代码总没有加入StringBuilder类,因为它高效,编译器自动使用了它。从上边看出编译器创建了一个StringBuilder对象,用以构建最终的String对象,并为每个字符串调用一次StringBuilider的append()方法,总计4次。最后调用StringBuilder的toString()方法生成结果,并存为s(使用的命令是astore_2)。
要有对比才能看出性能的区别,
public class WhitherStringBuilder {
public String implicit(String[] fields) {
String result = "";
for (int i = 0; i < fields.length; i++) {
result += fields[i];
}
return result;
}
public String explicit(String[] fields) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < fields.length; i++) {
result.append(fields[i]);
}
return result.toString();
}
}
D:\Download>javap -c WhitherStringBuilder.class
public java.lang.String implicit(java.lang.String[]);
Code:
0: ldc #2 // String
2: astore_2
3: iconst_0
4: istore_3
5: iload_3
6: aload_1
7: arraylength
8: if_icmpge 38
11: new #3 // class java/lang/StringBuilder
14: dup
15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
18: aload_2
19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: aload_1
23: iload_3
24: aaload
25: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore_2
32: iinc 3, 1
35: goto 5
38: aload_2
39: areturn
public java.lang.String explicit(java.lang.String[]);
Code:
0: new #3 // class java/lang/StringBuilder
3: dup
4: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
7: astore_2
8: iconst_0
9: istore_3
10: iload_3
11: aload_1
12: arraylength
13: if_icmpge 30
16: aload_2
17: aload_1
18: iload_3
19: aaload
20: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: pop
24: iinc 3, 1
27: goto 10
30: aload_2
31: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34: areturn
}
首先看implicit()方法,第8行到第35行构成一个循环体。第8行:队堆栈中的操作数进行“大于或等于的整数比较运算”,循环结束时跳到第38行。第35行:返回循环体的起始点(第5行)。可以看到StringBuilder在循环体内构造,这就意味着没经过一次循环就会产生一个新的StringBuilder对象。
再来看explicit()方法,这个方法的不仅循环代码更简短,更简单,而且它只生成了一个StringBuilder对象。同时显式地创建StringBuilder还允许预先为其指定大小,准确知道大小,预先分好,避免多次重新分配缓冲。
因此,字符串比较简单,可使用String类,要是比较复杂或在循环体内使用建议使用StringBuilder类。