在Java中何时使用StringBuilder

398
在Java中,通常使用StringBuilder进行字符串连接是更好的选择。但这种做法总是适用吗?
我的意思是,与使用+运算符连接两个字符串相比,创建StringBuilder对象、调用append()方法并最终调用toString() 方法的开销是否已经小于连接现有字符串?或者仅建议多于两个字符串的情况下使用StringBuilder呢?
如果存在这样的阈值,它取决于什么(也许是字符串长度,但以哪种方式)?
最后,在类似连接两个、三个或四个字符串这样较小的情况下,你会为了StringBuilder的性能而牺牲可读性和简洁性吗?
过时的Java优化技巧以及Java城市传说中都提到了对于常规连接,显式使用StringBuilder被认为是过时的。

1
请参阅此博客文章以获取详细说明:http://www.rationaljava.com/2015/02/the-optimum-method-to-concatenate.html - Dan
9个回答

558

如果您在循环中使用字符串拼接,就像这样:

String s = "";
for (int i = 0; i < 100; i++) {
    s += ", " + i;
}

如果你需要处理大量的字符串拼接操作,建议使用 StringBuilder 而不是 StringBuffer,因为它速度更快且占用的内存更少。

如果你只有一行代码需要进行字符串拼接操作,

String s = "1, " + "2, " + "3, " + "4, " ...;

那么你可以使用 String,因为编译器会自动使用 StringBuilder


33
现在,我几乎从不使用除字符串之外的其他东西了。虽然我曾经为此进行过辩护,但随着JVM的进步,幸运的是它几乎再也不必要了。如果您查看上面代码的字节码(至少使用JDK > 1.6u22),您会注意到一切都被StringBuilder替换了。现在它不仅仅是单个语句了,而且实际上相当复杂。我对真实代码进行了几个测试案例,事实上我没有发现任何情况下它不使用内部的StringBuilder。非常好。 - haylem
118
@haylem: 你确定吗?- 我已经用1.6.0.21版本检查过了,当然String循环也使用了StringBuilder,但仅用于连接字符串(s += ", " + i;),所以它为每个循环创建一个新的StringBuilder!- 因此它比只在循环外创建一次的StringBuilder,并在循环中仅调用其append方法要慢得多。 - Ralph
73
@Ralph:抱歉,上周很忙,但我最终还是找到时间去查找了,正如我之前提到的一样,我确实错了。它确实在每次循环转动时创建一个 StringBuilder。真糟糕。我可以想象有些情况下这样做更好,但我认为他们可以检测一些情况,实现一个增强路径检测。所以还有改进的空间 : )。谢谢你让我验证了这个。 - haylem
3
我使用jdk 1.8.0进行了测试——它的字节码相同。由于对象分配现在非常便宜,所以我有些怀疑性能会有很大提高。然而,将其更改为显式StringBuilder 1实例后,在1000次迭代中将执行时间从约120ms降至0-1ms。因此,在类似情况下,StringBuilder仍然对于关键位置是必要的。 - serg.nechaev
3
真实生活案例,我需要在内存中修改一个大小为10 MB的HTML文档并将其发送给客户端。我创建了一个字符串更改表,并使用StringBuilder构建结果,因为我需要进行许多复杂的更改。与简单的“String + String”相比,速度提升非常大,最终结果可以在不到10秒的时间内处理HTML。更改表可能需要进行数千到数万次更改,因此创建和销毁如此多的自动StringBuilder对象的开销非常大。 - Michael Shopsin
显示剩余13条评论

54

Ralph的回答非常棒。我更倾向于使用StringBuilder类来构建/装饰字符串,因为它的用法更像是构建器模式。

public String decorateTheString(String orgStr){
            StringBuilder builder = new StringBuilder();
            builder.append(orgStr);
            builder.deleteCharAt(orgStr.length()-1);
            builder.insert(0,builder.hashCode());
            return builder.toString();
}
它可以用作帮助器/构建器来构建字符串,而不是字符串本身。

3
我认为StringBuilder和Builder模式没有任何关系,名称的相似只是巧合。据我所知。 - nanosoft
1
你为什么要将建造者模式和字符串构建器混合使用?我猜你是想解释编码最佳实践。 - Harpreet Sandhu - TheRootCoder
1
为什么这个答案会被点赞?所提供的示例对 StringBuilder 的使用很糟糕(尽管已知大小,但未预先分配,插入操作会进行数组复制)。这比 + 运算符更好在哪里?并没有回答 OP 的问题。 - coffee_machine

45

一般来说,应该始终使用可读性更高的代码,只有在性能存在问题时才进行重构。在这个特定情况下,大多数最近版本的JDK实际上会将代码优化为StringBuilder版本。

只有在循环中进行字符串连接或者在编译器不能轻易优化的一些复杂代码中,你通常才需要手动进行操作。


7

5

一些编译器可能不会将任何字符串连接替换为 StringBuilder 的等效形式。在依赖于编译时优化之前,请确保考虑您的源代码将使用哪些编译器。


2
有趣,我不知道那个。你能提供一下哪些编译器会替换连接操作符,哪些不会吗? - kostja
1
不,我没有那个信息。我只是说要小心依赖编译时优化。如果你确定所有需要的编译器都提供了某些优化,那太好了!如果没有,最好假设它们没有。但最重要的是,只优化需要优化的部分。 - William Morrison

5

对于两个字符串的拼接,使用concat()方法更快,其它情况下使用StringBuilder是更好的选择。详细解释请参见我的回答:concatenation operator (+) vs concat()


4

加号运算符在内部使用public String concat(String str)方法。此方法复制两个字符串的字符,因此具有与两个字符串长度成比例的内存需求和运行时复杂度。StringBuilder更有效率。

然而,我已经阅读了这里,在Java 4之后的编译器中,使用加号进行连接的代码已更改为StringBuilder。因此,这可能根本不是问题。(尽管如果我在代码中依赖它,我会真正检查这个声明!)


3

字符串拼接的问题在于它会导致复制字符串对象并伴随着所有相关成本。StringBuilder不是线程安全的,因此比Java 5之前首选的StringBuffer更快。一般来说,你不应该在循环中进行字符串拼接,尤其是在经常调用的情况下。如果只偶尔进行几次拼接,这不会对你造成太大影响,当然这取决于你的性能要求。如果你正在进行实时处理,就需要非常小心。


2
微软认证材料也提到了这个问题。在.NET世界中,使用StringBuilder对象比简单地连接2个String对象更有效率。我认为Java字符串的情况应该也差不多。

我猜编译器优化类似于Java,可以缓解性能影响,或者认证材料暗示了有效的差异? - kostja
该材料明确指出,在进行1或2个字符串连接(分别为2或3个字符串)的情况下,使用StringBuilder是不太高效的实现方式。 - Babak Naffas
似乎与Java不同。无论如何,谢谢。 - kostja

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