Java8 的 String Concatenate 比 Str
2020-02-24 本文已影响0人
shengjk1
问题1:
文章说,大量 String + 连接比 通过 相应的StringBuilder 连接慢,要是在 Java7 之前我信,可以在 Java8 以及之后,编译器自动帮助我们把 + 优化成 StringBuilder (StringBuffer) 了。难道 Java8 的 String Concatenate 比 StringBuilder (StringBuffer) 慢?带着这样的疑问,决定好好的亲自试验一番。
/**
* @author shengjk1
* @date 2020/1/4
*/
public class Test {
public static void main(String[] args) {
int loopMax = 100000;
long start = System.currentTimeMillis();
String res = "";
for (int i = 0; i < loopMax; i++) {
res += i;
}
long end = System.currentTimeMillis();
System.out.println((end - start));
System.out.println("res" + res);
StringBuilder builder = new StringBuilder();
start = System.currentTimeMillis();
res = "";
builder.append(res);
for (int i = 0; i < loopMax; i++) {
builder.append(i);
}
end = System.currentTimeMillis();
System.out.println((end - start));
System.out.println("bulider" + builder.toString());
}
}
res: 39733 ms
bulider: 5 ms
注意是大量字符串的连接,特别是成为 热代码 之后,少量的字符串连接的差距就更显现不出来了。
问题来了,明明在 Java8 中 编译器将 String Concatenate 优化成了 StringBuilder ,为何差距还是这么明显?我们分别单独编译 String Concatenate 和 StringBuilder,然后分别单独看一下它们对应的机器指令是什么。
String Concatenate
long start = System.currentTimeMillis();
String res = "";
for (int i = 0; i < loopMax; i++) {
res += i;
}
long end = System.currentTimeMillis();
System.out.println((end - start));
System.out.println("res" + res);
Code:
0: ldc #2 // int 100000
2: istore_1
3: invokestatic #3 // Method java/lang/System.currentTimeMillis:()J
6: lstore_2
7: ldc #4 // String
9: astore 4
11: iconst_0
12: istore 5
#for 循环开始
14: iload 5
16: iload_1
17: if_icmpge 48
20: new #5 // class java/lang/StringBuilder
23: dup
24: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
27: aload 4
29: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
32: iload 5
34: invokevirtual #8 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
37: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
40: astore 4
42: iinc 5, 1
45: goto 14
# for 循环结束
# 在 for 循环结束和开始当中,每遍历一次都会创建一个 StringBuilder 对象,与下面的代码相比这就是速度慢的地方
48: invokestatic #3 // Method java/lang/System.currentTimeMillis:()J
51: lstore 5
53: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
56: lload 5
58: lload_2
59: lsub
60: invokevirtual #11 // Method java/io/PrintStream.println:(J)V
63: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
66: new #5 // class java/lang/StringBuilder
69: dup
70: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
73: ldc #12 // String res
75: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
78: aload 4
80: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
83: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
86: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
89: return
StringBuilder
StringBuilder builder = new StringBuilder();
long start = System.currentTimeMillis();
String res = "";
builder.append(res);
for (int i = 0; i < loopMax; i++) {
builder.append(i);
}
long end = System.currentTimeMillis();
System.out.println((end - start));
System.out.println("bulider" + builder.toString());
Code:
0: ldc #2 // int 100000
2: istore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: astore_2
11: invokestatic #5 // Method java/lang/System.currentTimeMillis:()J
14: lstore_3
15: ldc #6 // String
17: astore 5
19: aload_2
20: aload 5
22: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
25: pop
26: iconst_0
27: istore 6
# for 循环开始
29: iload 6
31: iload_1
32: if_icmpge 48
35: aload_2
36: iload 6
38: invokevirtual #8 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
41: pop
42: iinc 6, 1
45: goto 29
# for 循环结束
48: invokestatic #5 // Method java/lang/System.currentTimeMillis:()J
51: lstore 6
53: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
56: lload 6
58: lload_3
59: lsub
60: invokevirtual #10 // Method java/io/PrintStream.println:(J)V
63: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
66: new #3 // class java/lang/StringBuilder
69: dup
70: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
73: ldc #11 // String bulider
75: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
78: aload_2
79: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
82: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
85: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
88: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
91: return
最关键的部分其实是 for 循环部分,仔细看一下就会发现,对于 String Concatenate 每循环一次都会创建一个 StringBuilder,并且会 append两次然后 toString,并把结果赋值给 res,StringBuilder每次的创建和初始化也会浪费大量的时间以及内存。而 StringBuilder 仅仅创建一次,append 一次,toString 一次。
这也就解释了明明编译器自动帮助我们把 + 优化成 StringBuilder 了却还是 比 StringBuilder 慢的原因。