字符串拼接的三种方式
三种方式:1. “+”操作符;2.StringBuilder;3.StringBuffer
StringBuffer线程安全,StringBuilder非线程安全;为什么会这样呢?话不多说,直接上源码:
//StringBuffer的append方法
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
//StringBuilder的append方法
public StringBuilder append(int i) {
super.append(i);
return this;
}
从截取的部分源码中可以看到StringBuffer的append方法通过关键词synchronized修饰,StringBuilder的append方法并没有任何用来保证安全的操作;结论显而易见:StringBuffer线程安全,StringBuilder非线程安全。线程安全意味着消耗更多的性能,在效率上StringBuilder高于StringBuffer。
由于在实际项目中,非线程安全的字符串拼接应用较多一些,因此重点说下 “+”操作符 和StringBuilder的区别(最重要的原因还是网上有很多关于 “+”操作符 和StringBuilder的区别的说法让人云里雾里,解答不尽如人意)。
我们先通过现象看本质,话不多说,来个测试代码:
public static void main(String[] args) {
final int num = 1000;
long start = System.currentTimeMillis();
String str = "";
for (int i = 0; i < num; i++) {
str = str + i;
}
long center = System.currentTimeMillis();
StringBuilder b = new StringBuilder();
for (int i = 0; i < num; i++) {
b.append(i);
}
System.out.println("+操作符所花时间:" + (center - start) +
"ms;StringBuilder拼接所花时间:" + (System.currentTimeMillis() - center) + "ms");
}
运行结果:+操作符所花时间:7ms;StringBuilder拼接所花时间:0ms;由于运行机器等的原因,运行结果不一定相同,但肯定大同小异(num的值不宜设置过小,太小啦时间差异难以体现)。难道StringBuilder拼接的运行效率要高于+操作符的拼接效率?其实不然,从源码的角度很难说清,既然如此,那就只能从更底层的东西说起了,话不多说,直接上代码:
//测试代码
public static void main(String[] args) {
new Test1().m1();
new Test1().m2();
}
public void m1() {
String str = "";
for (int i = 0; i < 5; i++) {
str = str + i;
}
System.out.println(str);
}
public void m2() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5; i++) {
sb.append(i);
}
System.out.println(sb.toString());
}
javap命令:
javap -v Test1.class > Test1.txt
通过javap命令生成的字节码文件Test1.txt(代码一样,产生的字节码文件也会一样,截取部分重要的):
public void m1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: ldc #6 // String
2: astore_1
3: iconst_0
4: istore_2
5: iload_2
6: iconst_5
7: if_icmpge 35
10: new #7 // class java/lang/StringBuilder
13: dup
14: invokespecial #8 // Method java/lang/StringBuilder."<init>":()V
17: aload_1
18: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: iload_2
22: invokevirtual #10 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
25: invokevirtual #11 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
28: astore_1
29: iinc 2, 1
32: goto 5
35: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
38: aload_1
39: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
42: return
LineNumberTable:
line 16: 0
line 17: 3
line 18: 10
line 17: 29
line 20: 35
line 21: 42
LocalVariableTable:
Start Length Slot Name Signature
5 30 2 i I
0 43 0 this Lcom/gyt/manager/test/Test1;
3 40 1 str Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 253 /* append */
offset_delta = 5
locals = [ class java/lang/String, int ]
frame_type = 250 /* chop */
offset_delta = 29
public void m2();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: new #7 // class java/lang/StringBuilder
3: dup
4: invokespecial #8 // Method java/lang/StringBuilder."<init>":()V
7: astore_1
8: iconst_0
9: istore_2
10: iload_2
11: iconst_5
12: if_icmpge 27
15: aload_1
16: iload_2
17: invokevirtual #10 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
20: pop
21: iinc 2, 1
24: goto 10
27: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
30: aload_1
31: invokevirtual #11 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
37: return
LineNumberTable:
line 24: 0
line 25: 8
line 26: 15
line 25: 21
line 28: 27
line 29: 37
LocalVariableTable:
Start Length Slot Name Signature
10 17 2 i I
0 38 0 this Lcom/gyt/manager/test/Test1;
8 30 1 sb Ljava/lang/StringBuilder;
StackMapTable: number_of_entries = 2
frame_type = 253 /* append */
offset_delta = 10
locals = [ class java/lang/StringBuilder, int ]
frame_type = 250 /* chop */
offset_delta = 16
一大坨我自己看着都心累,直接看重点:
m1方法中:
10: new #7 // class java/lang/StringBuilder
...//此处省略
32: goto 5
m2方法中:
0: new #7 // class java/lang/StringBuilder
...//此处省略
24: goto 10
从字节码文件中不难看出,无论是“+操作符拼接”还是“StringBuilder拼接”本质上都是通过new一个StringBuilder对象来达到拼接的目的,结论一:“+操作符拼接”和“StringBuilder拼接”效率一致。
关注goto,也就是代码中循环的开始位置,我们不难发现,“+操作符拼接”每循环一次都需要new一个对象,而“StringBuilder拼接”仅仅只new了一次对象,对象的产生和回收都是需要时间的,所以结论二:在执行循环遍历操作时,“StringBuilder拼接”的执行效率远高于“+操作符拼接”的执行效率。