StringBuilder中append的参数是一个新的String对象,会不会被垃圾回收?
Is the argument of append in StringBuilder a new String object and will it be garbage collected?
在下面的代码中,当它到达注释时,如果GC不是运行,大约创建了1000个对象(根据OCA书),StringBuilder
被修改并保持为一个对象,空字符串 " "
被合并并重新使用,这就是所有的解释。参数 s
不是需要 GC 的 new String("s")
和 i
,它不会先转换为 new String
对象,然后与 " "
创建另一个 new String
对象,使它们在该行有 2 个 String 对象,符合 GC 以及 append
的参数,每个循环中总共有 3 个 String 对象。所以当代码到达注释行时总计 3000 个对象?
public class Mounds {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
String s = new String();
for (int i = 0; i < 1000; i++) {
s = " " + i;
sb.append(s);
}
// done with loop
}
}
编译器可能意识到变量 s
的作用域在循环内,因此它将赋值内联到 append()
中以生成
sb.append(" " + i)
所以现在只有 int 的转换在每次迭代中创建一个新的 String。
如果我们编译这段代码并查看生成的字节码,我们可以准确地检查它
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 调用的 return 值,并加载第一个 StringBuilder,然后再次加载 String。最后调用第一个 StringBuilder 的 append 方法将新字符串添加到 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方法被调用了两次,所以这里没有分配内存,这样应该会更好。
最佳用法是:
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;
将两个字符串连接成一个新字符串。一项任务应该留给 StringBuilder,因为这是它的唯一目的。
- 附加字符
' '
比字符串 " "
更有效。
new StringBuilder(4000)
具有可以在这里使用的初始容量,防止在追加时间歇性地重新分配。 1000 个数字,其中 900 个是 3 位数字,加上一个 space,将适合 4000 个字符。
在下面的代码中,当它到达注释时,如果GC不是运行,大约创建了1000个对象(根据OCA书),StringBuilder
被修改并保持为一个对象,空字符串 " "
被合并并重新使用,这就是所有的解释。参数 s
不是需要 GC 的 new String("s")
和 i
,它不会先转换为 new String
对象,然后与 " "
创建另一个 new String
对象,使它们在该行有 2 个 String 对象,符合 GC 以及 append
的参数,每个循环中总共有 3 个 String 对象。所以当代码到达注释行时总计 3000 个对象?
public class Mounds {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
String s = new String();
for (int i = 0; i < 1000; i++) {
s = " " + i;
sb.append(s);
}
// done with loop
}
}
编译器可能意识到变量 s
的作用域在循环内,因此它将赋值内联到 append()
中以生成
sb.append(" " + i)
所以现在只有 int 的转换在每次迭代中创建一个新的 String。
如果我们编译这段代码并查看生成的字节码,我们可以准确地检查它
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 调用的 return 值,并加载第一个 StringBuilder,然后再次加载 String。最后调用第一个 StringBuilder 的 append 方法将新字符串添加到 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方法被调用了两次,所以这里没有分配内存,这样应该会更好。
最佳用法是:
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;
将两个字符串连接成一个新字符串。一项任务应该留给 StringBuilder,因为这是它的唯一目的。- 附加字符
' '
比字符串" "
更有效。 new StringBuilder(4000)
具有可以在这里使用的初始容量,防止在追加时间歇性地重新分配。 1000 个数字,其中 900 个是 3 位数字,加上一个 space,将适合 4000 个字符。