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 个字符。