Java:String concat vs StringBuilder - 优化,那我该怎么办?

50
这个答案中,它表示字符串连接已经被优化为StringBuilder操作,因此当我编写代码时,是否有必要在源代码中编写StringBuilder代码?请注意,我的用例不同于OP的问题,因为我正在连接/添加数百到数千行。
为了让自己更清楚:我很清楚每种方法的区别,只是我不知道是否值得实际编写StringBuilder代码,因为它不太易读,并且当它的表亲String类在编译过程中自动转换时,效率也应该会更慢。

1
这里有一篇很好的分析文章:http://www.javacodegeeks.com/2013/03/java-stringbuilder-myth-debunked.html - marcolopes
4个回答

119

我认为在使用StringBuilder+时,取决于你使用的上下文。

通常情况下,在使用JDK 1.6及以上版本编译器时,编译器会自动使用StringBuilder将字符串连接在一起。

String one = "abc";
String two = "xyz";
String three = one + two;

这将会把 String three 编译为:

String three = new StringBuilder().append(one).append(two).toString();

这非常有帮助,可以节省一些运行时间。然而,这个过程并不总是最优的。以以下为例:

String out = "";
for( int i = 0; i < 10000 ; i++ ) {
    out = out + i;
}
return out;

如果我们将代码编译为字节码,然后再反编译生成的字节码,我们会得到类似以下内容:

String out = "";
for( int i = 0; i < 10000; i++ ) {
    out = new StringBuilder().append(out).append(i).toString();
}
return out;

编译器已经优化了内部循环,但肯定没有做到最佳的优化。为了改进我们的代码,我们可以使用:

StringBuilder out = new StringBuilder();
for( int i = 0 ; i < 10000; i++ ) {
    out.append(i);
}
return out.toString();

现在,使用 StringBuilder/StringBuffer 类编写代码可比编译器生成的代码更加优化,因此在需要高效代码的情况下需要使用它们。然而,当前的编译器在循环中连接字符串方面并不擅长,但是这可能会在未来发生改变。

您需要仔细查看需要手动应用 StringBuilder 的地方,并尽可能在不降低代码可读性的情况下使用它。

注意:我使用 JDK 1.6 编译代码,并使用 javap 程序反编译代码,该程序可以输出字节码。这相当容易解释,通常在优化代码时很有用。编译器确实在幕后更改您的代码,因此查看其作用总是很有趣的!


3
你的反编译代码(在第二个for循环中)是实际反编译的输出,还是你的猜测?如果你用一个含有“+”字符串连接的for/while循环进行调试,你会发现StringBuilder对象并不会在每次循环中重新创建,而实际上是重复使用同一个对象。否则,如果你用一个更重的对象来替代它(比如用StringBuilder代替String),你真正获得的优化是什么? - Spencer Kormos
2
我也想知道@SpencerKormos的问题的答案。编译器是否实际上为每个循环迭代创建一个新的StringBuilder?为什么这样做是最优的呢? - ryvantage
重新阅读答案和OP问题中的链接,Tom所说的是,如果concat操作发生在循环中,则编译器对整个代码块(包括for循环)没有优化。在这种情况下,您应该在for循环之前声明您的StringBuilder,并在循环内使用append(),使concat操作最有效(即Tom的最后一个代码块)。希望这有意义,感谢您让我回到这里! - Spencer Kormos
在Java的早期版本中,使用“+”连接字符串会被转换为Stringbuilder。具体请参考此链接:http://web.archive.org/web/20041205170952/http://java.sun.com:80/docs/books/jls/first_edition/html/15.doc.html#40226 - ihebiheb

3
你问题中的关键词是“据说更慢”。你需要确定这是否真的是一个瓶颈,然后看哪个更快。
如果你要编写此代码,但尚未编写,请先编写对你来说更清晰的内容,然后如果必要时再查看它是否成为了瓶颈。
虽然使用你认为更快的代码是有道理的,但如果两者同样易读,在没有需求的情况下花时间找出哪个更快是浪费时间的。在性能不可接受之前,易读性高于性能。

实际上,只要重写代码两种方式之一,可读性就没有太大差别,因为只是更改类声明并将concat()更改为append()。我想我会坚持使用StringBuilder来确保性能。 - Soyuz

2

这取决于具体情况,但是StringBuilder被认为速度稍快。如果您正在循环内进行连接操作,那么建议使用StringBuilder。

无论如何,我建议您对代码进行分析和基准测试(如果您正在进行大量的追加操作)。

但要小心:StringBuilder实例是可变的,不应在线程之间共享(除非您确实知道自己在做什么),而String则是不可变的。


重写了问题。我明白你说的,我只是想知道,我应该具体写什么? - Soyuz
1
应该有一些证据或来源来支持“StringBuilder 被认为速度略快”的观点。我猜这是因为缺乏同步开销,但仍然希望能提供一些来源。 - Jeroen Vannevel

0
我可能误解了你的问题,但是在追加字符串时,StringBuilder更快。因此,如果您正在追加“数百到数千行”,则绝对应该使用StringBuilder(如果您正在运行多线程应用程序,则应使用StringBuffer)。
(评论中有更详细的答案)

1
现代/成熟的Java编译器显然足够聪明,能够将连接操作转换为StringBuilder追加操作,那么在代码中使用StringBuilder有什么意义呢?既然字符串连接已经被转换为StringBuilder操作,StringBuilder又不如字符串连接易读,为什么还要使用它呢? - Soyuz
3
哦,现在我明白你的观点了,伙计。是的,你是对的:编译器会识别优化代码的机会,并且大多数情况下会将常规的String.concat替换为StringBuilder——但并非总是如此。你不能依赖编译器。如果代码很复杂,编译器可能会错过优化的机会。如果我是你,我会选择稍微牺牲可读性来确保优化。 - user1348997

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