在StringBuilder中追加一个新的String对象的参数是否会被垃圾收集?

时间:2018-03-06 21:35:10

标签: java string garbage-collection stringbuilder

在下面的代码中,当它到达注释时,如果GC未运行,则创建大约1000个对象(根据OCA书),StringBuilder被修改并保留为一个对象,空字符串" "被汇集并重新使用,这就是所有解释的内容。不是需要GCed的snew String("s")参数,i,它不会首先转换为new String对象,然后与{{1}结合使用创建另一个" "对象,使它们成为该行的2个String对象,符合GC以及new String的参数,每个循环中共有3个String对象。所以代码到达注释行时是3000对象的总和?

append

3 个答案:

答案 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。