在下面的代码中,当它到达注释时,如果GC未运行,则创建大约1000个对象(根据OCA书),StringBuilder
被修改并保留为一个对象,空字符串" "
被汇集并重新使用,这就是所有解释的内容。不是需要GCed的s
和new String("s")
参数,i
,它不会首先转换为new String
对象,然后与{{1}结合使用创建另一个" "
对象,使它们成为该行的2个String对象,符合GC以及new String
的参数,每个循环中共有3个String对象。所以代码到达注释行时是3000对象的总和?
append
答案 0 :(得分:4)
如果我们编译这段代码并查看生成的字节码,我们可以检查完全
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,这意味着文字被推送到堆栈,在这种情况下它是文字字符串“”。
然后真正的魔法发生了。调用第二个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。另外,一个新的String将由中间StringBuilder的toString方法创建,因此总共有2000个对象。
更好的版本看起来像这样:
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方法被调用两次,因此这里没有分配内存,这应该更好。
答案 1 :(得分:1)
最佳用法是:
StringBuilder sb = new StringBuilder(4000);
for (int i = 0; i < 1000; ++i) {
sb.append(' ').append(i);
}
... do something with sb.toString()
如:
String s = new String();
创建一个不必要的空字符串。与String s = "";
相同。 (不考虑优化。)s = " " + i;
将两个字符串连接成一个新的String。一个任务应该留给StringBuilder,因为这是它的唯一目的。' '
比字符串" "
更有效。new StringBuilder(4000)
具有可在此处使用的初始容量,防止在追加时间歇性重新分配。 1000个数字,其中900个是3位数字,加上一个空格,将适合4000个字符。答案 2 :(得分:-1)
编译器可能会意识到变量s
的范围在循环内部,因此它将赋值内联到append()
以生成
sb.append(" " + i)
所以现在只有int的转换才会在每次迭代中创建一个新的String。