如果编译器会自动将字符串拼接转换为 StringBuilder,那么为什么还要明确地使用 StringBuilder?

41
我在想,既然编译器在执行字符串拼接时内部使用了 StringBuilder,那么使用 StringBuilder 还有什么意义呢?如果字符串拼接已经完成了任务,为什么还要费心使用 StringBuilder?是否还有其他特定原因?此外,这篇文章 StringBuilder vs String concatenation in toString() in Java 与此话题重复,请注意。
2个回答

71

正如您提到的那样,您不应该使用 StringBuilder 来代替一个简单的字符串连接表达式,比如 a + " = " + b。后者输入更快,易于阅读,并且编译器会在内部使用 StringBuilder,所以重写它并没有性能上的优势。

然而,如果您正在循环中连接大量字符串,则StringBuilder是有用的。以下代码效率低下。它需要 O(n2) 的时间运行并创建许多临时字符串。

String result = "";
for (int i = 0; i < foo.length; ++i)
{
    result += bar(foo[i]);  // Bad
}

可以尝试使用这个方法替代:

StringBuilder sb = new StringBuilder();
for (int i = 0; i < foo.length; ++i)
{
    sb.append(bar(foo[i]));
}
String result = sb.toString();

编译器只优化简单的a + b + c表达式。它无法自动优化上述代码。


4
是的,实际上会这样。执行 result += bar(foo[i]) 会生成类似以下代码(假设是 javac):result = new StringBuilder().append(result).append(foo[i]).toString()。请注意,这比使用一个 StringBuilder 要繁琐得多。 - obataku
3
因为在第一个示例中,Java的优化器不知道如何保留一个StringBuilder来执行循环中的所有连接操作——它无法预见到您始终会将其连接到从未出现在循环体外的相同字符串上,因此每次必须添加到'result'时都会创建一个新的。 - cHao
4
使用 += 操作符会在每次迭代时创建一个新的 StringBuilder 对象,将源字符串复制一份,再连接你要添加的内容(这也可能需要一次复制),最后将其转换为一个 String(另一次复制)。使用一个 StringBuilder 对象可以避免不必要的复制。 - obataku
1
那么对于 result = result + bar(foo[i]); 这一行代码呢?它最终的结果也是一样的吗? - peter
2
@cHao,是的,我完全了解理论——但我正在讨论手头的编译器(假设它是javac)。 javac不再明确执行任何真正的优化,并且它从未执行过如此复杂的优化。它所做的是常量折叠和DCE传递。 - obataku
显示剩余4条评论

3
你认为字符串连接使用 StringBuilder 内部实现的假设基于什么?也许简单的连接可以被优化,但这绝对不行:
String s = "";

for (int i = 0; i < 1000; i++){
  for (int j = 0; j < 1000; j++){
    s+= "" + i + j
}
}

1
那么这在内部会是什么? - peter
假设编译器是javac。@user1389813 s = new StringBuilder.append("").append(i).append(j).toString(); - obataku
每次执行连接操作时,它都会创建一个新的 StringBuilder,所以这就是为什么它不好且慢,对吗? - peter
1
我不会说它一定很“慢”(我不主张过早优化)……只是这不是一个好习惯。 - obataku
@user1389813,问题不仅在于它创建了一个新的StringBuilder,而是它创建了一个新的StringBuilder,然后从s字符串复制所有字符,只有然后附加ij。每次循环,您都会复制先前拥有的所有字符(它们本身已从先前的循环中复制),从而获得O(n ^ 2)性能。通过从循环体中删除新的StringBuilder,可以消除所有冗余的复制。 - yshavit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接