为什么在有String的情况下要使用StringBuilder?

92

我第一次接触到 StringBuilder,很惊讶因为 Java 已经有一个非常强大的String类可以进行追加了。

为什么还需要一个新的String类呢?

我应该去哪里学习更多关于StringBuilder的内容呢?


我只想指出这实际上曾经是我的一道面试题。他们问我如何填充一个大字符串..... - preOtep
StringBuilder在追加字符串时比String快上百倍。这里有一篇文章,附带了一个可以运行的Java脚本,你可以自己试一下:https://blog.terresquall.com/2023/08/try-it-yourself-compare-performance-java-string-stringbuilder-stringbuffer/ - John Doe
9个回答

186

String 不允许追加。在 String 上调用每个方法都会创建一个新对象并返回它。这是因为 String 是不可变的 - 它无法改变其内部状态。

另一方面,StringBuilder 是可变的。当您调用 append(..) 时,它会更改内部 char 数组,而不是创建一个新的字符串对象。

因此,最好使用:

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 500; i ++) {
    sb.append(i);
}

可以使用 str.join() 方法代替 str += i,这样可以避免创建500个新的字符串对象。

请注意,在示例中我使用了循环。正如Helios在评论中指出的那样,编译器会自动将类似于 String d = a + b + c 的表达式转换为类似于以下的内容。

String d = new StringBuilder(a).append(b).append(c).toString();

还要注意,除了 StringBuilder 之外,还有一个 StringBuffer。区别在于前者具有同步的方法。如果您将其用作局部变量,请使用 StringBuilder。如果可能会被多个线程访问,则使用 StringBuffer(这种情况比较少见)。


28
你可以添加:因此,StringBuilder 寻求更好的性能,并且 "Java compiler" 会将像 A + B + C 这样的表达式替换为新的 StringBuilder(A).append(B).append(C).toString(),以避免对象创建所带来的性能损失。 :) - helios
超喜欢“不可变”对象的回溯。 - Gary Tsui
很好的回答。然而,我所缺少的是为什么我们不能让编译器在大多数情况下(包括for循环中)自动判断何时使用Stringbuilder,这样开发人员就不需要考虑它。 :) - worldsayshi
1
好的回答。我想要添加这些内容:String 是不可变的,因为持有 String 的 char[] 数组(即 value)被声明为 final,但是在 StringBuilder 中,持有 String 的 char[] 数组(即 value)并不是 final 的。你可以对持有 String 的数组进行更改,这在 StringBuilder 中是成立的。 - Deen John
@AdiyatMubarak 是的。一个不可变对象的状态是不能被改变的,所以例如 toLowerCase 无法改变它所调用的当前字符串。因此,它会从当前字符串创建一个新的字符串。 - Bondolin
显示剩余3条评论

70

以下是一个具体示例,说明为什么 -

int total = 50000;
String s = ""; 
for (int i = 0; i < total; i++) { s += String.valueOf(i); } 
// 4828ms

StringBuilder sb = new StringBuilder(); 
for (int i = 0; i < total; i++) { sb.append(String.valueOf(i)); } 
// 4ms

可以看到性能的差异很大。


Ps. 我在我的Macbook Pro双核上运行了这个程序。 - Amir Raminfar
26
这解释了为什么在有String的情况下需要使用StringBuilder,但这并不解释StringBuilder为什么如此快。但这并不是问题所在,因此这是一个有效的答案。 - Kerem Baydoğan
2
@krmby - 同意。回答为什么真正意味着另一个问题。 - Amir Raminfar
15
为了公平比较,我认为你应该在末尾加入执行 s = sb.ToString(); 的时间,以便两个示例都执行了相同的操作(结果是一个 string)。 - Scott Whitlock

19

String类是不可变的,而StringBuilder是可变的。

String s = "Hello";
s = s + "World";
上面的代码会创建两个对象,因为字符串是不可变的。
StringBuilder sb = new StringBuilder("Hello");
sb.append("World");

上述代码只会创建一个对象,因为StringBuilder不是不可变的。

教训:每当需要多次操作/更新/追加字符串时,请使用StringBuilder,因为与String相比,它更有效率。


8

StringBuilder是用于构建字符串的。特别是以一种非常高效的方式构建它们。String类适用于许多事情,但实际上在将新字符串组合成较小的字符串部分时,它的性能非常差,因为每个新字符串都是一个全新的、重新分配的字符串。(它是不可变的) StringBuilder保持相同的序列并修改它(可变)。


5

StringBuilder类是可变的,与String不同的是,它允许您修改字符串的内容而无需创建更多的String对象,当您需要大量修改字符串时,这可以提高性能。还有一个StringBuilder的对应物叫做StringBuffer,它也是同步的,因此非常适合多线程环境。

String的最大问题在于,任何与它相关的操作都会返回一个新的对象,例如:

String s1 = "something";
String s2 = "else";
String s3 = s1 + s2; // this is creating a new object.

5
确切地说,StringBuilder添加所有的字符串是O(N),而添加字符串则是O(N^2)。检查源代码可以发现,这是通过保持一个可变字符数组来实现的。 StringBuilder使用数组长度加倍技术来实现摊销O(N^2)的性能,代价是可能需要翻倍的内存。您可以在最后调用trimToSize来解决这个问题,但通常StringBuilder对象仅被暂时使用。如果提供一个良好的最终字符串大小的起始猜测,可以进一步提高性能。

3

效率。

每次连接字符串时,都会创建一个新的字符串。例如:

String out = "a" + "b" + "c";

这将创建一个新的临时字符串,将"a"和"b"复制到其中,结果为"ab"。然后它创建另一个新的临时字符串,将"ab"和"c"复制到其中,结果为"abc"。这个结果然后被分配给out
结果是一个时间复杂度为O(n²)(二次方)的Schlemiel the Painter's algorithm
另一方面,StringBuilder允许您就地添加字符串,根据需要调整输出字符串的大小。

许多JVM实现将编译您的示例为StringBuilder,然后将最终结果转换为String。在这种情况下,它不会通过重复的String分配来组装。 - scottb

3

当你处理较大的字符串时,使用StringBuilder是很好的选择。它可以帮助你提高性能。

这里有一篇我发现很有用的文章

快速的谷歌搜索可以帮到你。现在你雇了7个人来为你做一个谷歌搜索。 :)


我们在这里不都是在做无偿工作吗? - Arefe
1
文章链接已经失效/损坏。 - d-coder

1

Java有String、StringBuffer和StringBuilder:

  • String:它是不可变的

  • StringBuffer:它是可变的且线程安全的

  • StringBuilder:它是可变的但不是线程安全的,自Java 1.5引入

例如String:

public class T1 {

    public static void main(String[] args){

        String s = "Hello";

        for (int i=0;i<10;i++) {

            s = s+"a";
            System.out.println(s);
        }
    }
}

}

输出:将创建10个不同的字符串,而不仅仅是1个字符串。

Helloa
Helloaa
Helloaaa
Helloaaaa
Helloaaaaa
Helloaaaaaa
Helloaaaaaaa
Helloaaaaaaaa 
Helloaaaaaaaaa 
Helloaaaaaaaaaa

StringBuilder 示例:只会创建一个 StringBuilder 对象。

public class T1 {

    public static void main(String[] args){

        StringBuilder s = new StringBuilder("Hello");

        for (int i=0;i<10;i++) {    
            s.append("a");
            System.out.println(s);
        }
    }
}

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