为什么
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());
}
我知道这里有很多类似的问题,但这些并不能真正回答我的问题。
你了解它的内部运作原理吗?
每次执行
stringA += stringB;
都会创建一个新字符串并将其分配给stringA,因此它将消耗内存(一个新字符串实例!)和时间(复制旧字符串+另一个字符串的新字符)。
StringBuilder
将在内部使用字符数组,当您使用 .append()
方法时,它将执行以下操作:
System.arraycopy
来复制数组中字符串的字符。就我个人而言,我认为每次分配一个新字符串(创建一个新的字符串实例,放入字符串等)在内存和速度方面可能会非常昂贵(在while/for等中)。特别是)。
在您的示例中,使用
StringBuilder
更好,但如果您需要(示例)像 .toString()
这样简单的东西,
public String toString() {
return StringA + " - " + StringB;
}
没有区别(好吧,在这种情况下,最好避免 StringBuilder 开销,这在这里毫无用处)。
Java 中的字符串是不可变的。这意味着对字符串进行操作的方法永远无法更改字符串的值。使用 += 进行字符串串联的工作原理是为一个全新的字符串(前两个字符串的串联)分配内存,并用这个新字符串替换引用。每个新的串联都需要构造一个全新的 String 对象。
相比之下,StringBuilder 和 StringBuffer 类被实现为可变字符序列。这意味着当您将新的字符串或字符附加到 StringBuilder 时,它只是更新其内部数组以反映您所做的更改。这意味着仅当字符串增长超过 StringBuilder 中已存在的缓冲区时才会分配新内存。
我可以列出一个非常好的例子来理解这一点(我的意思是我觉得这是一个很好的例子)。检查此处取自 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 是可变的,它只会追加,不会对消耗的内存造成重载。
问题在于您使用
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
减慢了速度。