如果我们编译这段代码并查看生成的字节码,我们可以准确地检查它
public static void main(java.lang.String[]) throws java.io.IOException;
Code:
0: new #19 // class java/lang/StringBuilder
3: dup
4: invokespecial #21 // Method java/lang/StringBuilder."<init>":()V
7: astore_1
8: new #22 // class java/lang/String
11: dup
12: invokespecial #24 // Method java/lang/String."<init>":()V
15: astore_2
16: iconst_0
17: istore_3
18: goto 47
21: new #19 // class java/lang/StringBuilder
24: dup
25: ldc #25 // String
27: invokespecial #27 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
30: iload_3
31: invokevirtual #30 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
34: invokevirtual #34 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
37: astore_2
38: aload_1
39: aload_2
40: invokevirtual #38 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
43: pop
44: iinc 3, 1
47: iload_3
48: sipush 1000
51: if_icmplt 21
54: return
我们关心的指令是从 21 到 40。在 21 中,创建了第二个 StringBuilder,我们稍后再讲。
在 25 中我们看到,有一个 ldc,这意味着一个字面量被压入堆栈,在本例中是字面量 String " "。
然后真正的魔法发生了。调用第二个 StringBuilder 的构造函数,它将堆栈中的文字作为参数。然后使用 iload_3 从局部变量数组加载 int i,然后调用第二个 StringBuilder 的 append 方法将 i 附加到它,然后调用 toString。使用 astore_2 和 aload_1 存储 toString 调用的返回值,并加载第一个 StringBuilder,然后再次加载 String。最后调用第一个 StringBuilder 的 append 方法,将新的 String 添加到 StringBuilder。
事实证明,在每个循环中都会创建一个新的 StringBuilder,因为每次使用 " " + i 时都必须创建一个 StringBuilder 来连接 String 和 int。另外中间StringBuilder的toString方法会创建一个新的String,所以总共会有2000个Object。
更好的版本应该是这样的:
for (int i = 0; i < 1000; i++) {
sb.append(' ');
sb.append(i);
}
这将创建以下字节码:
public static void main(java.lang.String[]) throws java.io.IOException;
Code:
0: new #19 // class java/lang/StringBuilder
3: dup
4: invokespecial #21 // Method java/lang/StringBuilder."<init>":()V
7: astore_1
8: new #22 // class java/lang/String
11: dup
12: invokespecial #24 // Method java/lang/String."<init>":()V
15: astore_2
16: iconst_0
17: istore_3
18: goto 37
21: aload_1
22: bipush 32
24: invokevirtual #25 // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;
27: pop
28: aload_1
29: iload_3
30: invokevirtual #29 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
33: pop
34: iinc 3, 1
37: iload_3
38: sipush 1000
41: if_icmplt 21
44: return
我们可以看到,现在只有一个StringBuilder,它的append方法被调用了两次,所以这里没有分配内存,这样应该会更好。