为什么 StringBuilder 比 String 快很多?

问题描述 投票:0回答:4

为什么

StringBuilder
比使用
+
运算符进行字符串连接要快得多?即使
+
运算符在内部是使用
StringBuffer
StringBuilder
实现的。

public void shortConcatenation(){
    long startTime = System.currentTimeMillis();
    while (System.currentTimeMillis() - startTime <= 1000){

        character += "Y";
    }

    System.out.println("short: " + character.length());
}

//// 使用字符串生成器

 public void shortConcatenation2(){
    long startTime = System.currentTimeMillis();
    StringBuilder sb = new StringBuilder();
    while (System.currentTimeMillis() - startTime <= 1000){

        sb.append("Y");
    }
    System.out.println("string builder short: " + sb.length());
}

我知道这里有很多类似的问题,但这些并不能真正回答我的问题。

java string stringbuilder
4个回答
27
投票

你了解它的内部运作原理吗?

每次执行

stringA += stringB;
都会创建一个新字符串并将其分配给stringA,因此它将消耗内存(一个新字符串实例!)和时间(复制旧字符串+另一个字符串的新字符)。

StringBuilder
将在内部使用字符数组,当您使用
.append()
方法时,它将执行以下操作:

  • 检查是否有任何可用空间可用于追加字符串
  • 再次进行一些内部检查并运行
    System.arraycopy
    来复制数组中字符串的字符。

就我个人而言,我认为每次分配一个新字符串(创建一个新的字符串实例,放入字符串等)在内存和速度方面可能会非常昂贵(在while/for等中)。特别是)。

在您的示例中,使用

StringBuilder
更好,但如果您需要(示例)像
.toString()
这样简单的东西,

public String toString() {
    return StringA + " - " + StringB;
}

没有区别(好吧,在这种情况下,最好避免 StringBuilder 开销,这在这里毫无用处)。


20
投票

Java 中的字符串是不可变的。这意味着对字符串进行操作的方法永远无法更改字符串的值。使用 += 进行字符串串联的工作原理是为一个全新的字符串(前两个字符串的串联)分配内存,并用这个新字符串替换引用。每个新的串联都需要构造一个全新的 String 对象。

相比之下,StringBuilder 和 StringBuffer 类被实现为可变字符序列。这意味着当您将新的字符串或字符附加到 StringBuilder 时,它只是更新其内部数组以反映您所做的更改。这意味着仅当字符串增长超过 StringBuilder 中已存在的缓冲区时才会分配新内存。


1
投票

我可以列出一个非常好的例子来理解这一点(我的意思是我觉得这是一个很好的例子)。检查此处取自 LeetCode 问题的代码:https://leetcode.com/problems/remove-outermost-parentheses/

1:使用字符串

public String removeOuterParentheses(String S) {

    String a = "";
    int num = 0;
    for(int i=0; i < S.length()-1; i++) {
        if(S.charAt(i) == '(' && num++ > 0) {
            a += "(";
        }
        if(S.charAt(i) == ')' && num-- > 1) {
            a += ")";
        }
    }
    return a;
}

现在,使用 StringBuilder。

public String removeOuterParentheses(String S) {

    StringBuilder sb = new StringBuilder();

    int a = 0;
    for(char ch : S.toCharArray()) {
        if(ch == '(' && a++ > 0) sb.append('(');
        if(ch == ')' && a-- > 1) sb.append(')');
    }
    return sb.toString();
}

两者的性能相差很大。第一个提交使用 String,而后一个提交使用 StringBuilder。

如上所述,理论是相同的。字符串按属性是不可变和同步的,即它的状态无法改变。例如,第二个方法的成本很高,因为每当使用串联函数或“+”时都会创建新的内存分配。它将消耗大量堆并且速度会变慢。相比之下,StringBuilder 是可变的,它只会追加,不会对消耗的内存造成重载。


0
投票

问题在于您使用

StringBuilder
实现了比编译器更高效的版本。尝试一下

while (System.currentTimeMillis() - startTime <= 1000) {
    character = new StringBuilder(character).append("Y").toString();
}

这将为您提供与

+=
类似的结果。问题在于,编译器不一定足够聪明,无法意识到它可以使用相同的
StringBuilder
执行所有操作,而不是重复转换为
String
然后返回
StringBuilder
。正如 others 已经指出的那样,使用单个
StringBuilder
会快得多,因为它只需要偶尔分配内存。

+=
版本可能仍然比那个版本做了更多的优化。但它会比你的版本更像那样。考虑

sb = new StringBuilder();
while (System.currentTimeMillis() - startTime <= 1000) {
    sb.append("Y");
    character = sb.toString();
}

几乎与您的版本相同,但速度就像在每次迭代中创建新的

StringBuilder
一样慢。显然是
toString
减慢了速度。

© www.soinside.com 2019 - 2024. All rights reserved.