【发布时间】:2010-09-09 02:28:35
【问题描述】:
有人告诉我使用StringBuffer 连接Java 中的字符串比使用+ 运算符来连接Strings 更有效。当你这样做时,引擎盖下会发生什么? StringBuffer 有什么不同?
【问题讨论】:
标签: java optimization string-concatenation
有人告诉我使用StringBuffer 连接Java 中的字符串比使用+ 运算符来连接Strings 更有效。当你这样做时,引擎盖下会发生什么? StringBuffer 有什么不同?
【问题讨论】:
标签: java optimization string-concatenation
原因是字符串不可变。它不是修改字符串,而是创建一个新字符串。
字符串池存储所有字符串值,直到垃圾收集器对其进行处理。
想想两个字符串是Hello 和how are you。
如果我们考虑字符串池,它有两个字符串。
如果您尝试将这两个字符串连接为,
string1 = string1+string2
现在创建一个新的字符串对象并将其存储在字符串池中。
如果我们尝试连接数千个单词,它会获得更多内存。解决方案是 StringBuilder 或 StringBuffer。它只能创建一个 Object 并且可以修改。因为两者都是可变的。那么不需要更多的内存。如果您认为线程安全,则使用 StringBuffer,否则使用 StringBuilder。
public class StringExample {
public static void main(String args[]) {
String arr[] = {"private", "default", "protected", "public"};
StringBuilder sb= new StringBuilder();
for (String value : arr) {
sb.append(value).append(" ");
}
System.out.println(sb);
}
}
输出:私有默认受保护的公共
【讨论】:
更多信息:
StringBuffer 是一个线程安全的类
public final class StringBuffer extends AbstractStringBuilder
implements Serializable, CharSequence
{
// .. skip ..
public synchronized StringBuffer append(StringBuffer stringbuffer)
{
super.append(stringbuffer);
return this;
}
// .. skip ..
}
但是 StringBuilder 不是线程安全的,因此如果可能的话使用 StringBuilder 会更快
public final class StringBuilder extends AbstractStringBuilder
implements Serializable, CharSequence
{
// .. skip ..
public StringBuilder append(String s)
{
super.append(s);
return this;
}
// .. skip ..
}
【讨论】:
在某些情况下,由于编译器执行了优化,这已经过时了,但普遍的问题是这样的代码:
string myString="";
for(int i=0;i<x;i++)
{
myString += "x";
}
将如下所示(每个步骤都是下一个循环迭代):
如您所见,每次迭代都必须再复制一个字符,导致我们在每个循环中执行 1+2+3+4+5+...+N 次操作。这是一个 O(n^2) 操作。但是,如果我们事先知道我们只需要 N 个字符,我们可以在一次分配中完成,只复制我们正在使用的字符串中的 N 个字符 - 只需 O(n) 操作。
StringBuffer/StringBuilder 避免了这种情况,因为它们是可变的,因此不需要一遍又一遍地复制相同的数据(只要它们的内部缓冲区中有空间可以复制)。他们避免执行与附加数量成正比的分配和复制,方法是将缓冲区过度分配其当前大小的一部分,从而提供摊销的 O(1) 附加。
但值得注意的是,编译器通常能够自动将代码优化为 StringBuilder 样式(或者更好,因为它可以执行常量折叠等)。
【讨论】:
一个不应该比另一个快。在 Java 1.4.2 之前不是这样,因为当使用“+”运算符连接两个以上的字符串时,会在构建最终字符串的过程中创建中间 String 对象。
然而,正如JavaDoc for StringBuffer 所述,至少从Java 1.4.2 开始,使用“+”运算符编译为创建StringBuffer 和append() 给它的许多字符串。所以显然没有区别。
但是,在循环中使用将字符串添加到另一个字符串时要小心!例如:
String myString = "";
for (String s : listOfStrings) {
// Be careful! You're creating one intermediate String object
// for every iteration on the list (this is costly!)
myString += s;
}
但是请记住,通常用“+”连接几个字符串比append()ing 他们都干净。
【讨论】:
AFAIK 它取决于 JVM 的版本,在 1.5 之前的版本中,使用“+”或“+=”实际上每次都复制整个字符串。
请注意,使用 += 实际上会分配字符串的新副本。
正如在循环中使用 + 所指出的那样,涉及复制。
当连接的字符串是编译时常量时,在编译时连接,所以
String foo = "a" + "b" + "c";
已编译为:
String foo = "abc";
【讨论】:
对于简单的连接,例如:
String s = "a" + "b" + "c";
使用StringBuffer 毫无意义——正如 jodonnell 指出的那样,它将被巧妙地翻译成:
String s = new StringBuffer().append("a").append("b").append("c").toString();
但是在一个循环中连接字符串是非常糟糕的,例如:
String s = "";
for (int i = 0; i < 10; i++) {
s = s + Integer.toString(i);
}
在此循环中使用字符串会在内存中生成 10 个中间字符串对象:“0”、“01”、“012”等。在使用StringBuffer 编写相同的内容时,您只需更新StringBuffer 的一些内部缓冲区,并且您不会创建那些您不需要的中间字符串对象:
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 10; i++) {
sb.append(i);
}
实际上,对于上面的示例,您应该使用 StringBuilder(在 Java 1.5 中引入)而不是 StringBuffer - StringBuffer 稍微重一些,因为它的所有方法都是同步的。
【讨论】:
当您连接两个字符串时,实际上是在 Java 中创建了第三个 String 对象。使用 StringBuffer(或 Java 5/6 中的 StringBuilder)更快,因为它使用内部字符数组来存储字符串,并且当您使用其中一个 add(...) 方法时,它不会创建新的 String目的。相反,StringBuffer/Buider 附加了内部数组。
在简单的串联中,使用 StringBuffer/Builder 还是 '+' 运算符串联字符串并不是真正的问题,但是在进行大量字符串串联时,您会发现使用 StringBuffer/Builder 更快。
【讨论】:
Java 语言规范的String Concatenation Operator + 部分为您提供了有关 + 运算符为何如此缓慢的更多背景信息。
【讨论】:
如前所述,String 对象是不可变的,这意味着一旦创建(见下文)就无法更改。
String x = new String("something"); // 或
字符串 x = "某事";
因此,当您尝试连接 String 对象时,会获取这些对象的值并将其放入新的 String 对象中。
如果您改为使用可变的 StringBuffer,您会不断地将值添加到 char(基元)的内部列表中,该列表可以扩展或截断以适应所需的值。不创建新对象,仅在需要保存值时创建/删除新字符。
【讨论】:
我认为最简单的答案是:它更快。
如果您真的想了解所有底层知识,您可以随时查看源代码:
【讨论】:
因为字符串是不可变的,所以每次调用 + 运算符都会创建一个新的字符串对象并将字符串数据复制到新的字符串中。由于复制字符串所需的时间与字符串的长度呈线性关系,因此对 + 运算符的 N 次调用序列会导致 O(N2) 运行时间(二次)。
相反,由于 StringBuffer 是可变的,它不需要每次执行 Append() 时都复制 String,因此 N 个 Append() 调用的序列需要 O(N) 时间(线性)。如果您将大量字符串附加在一起,这只会在运行时产生显着差异。
【讨论】:
我认为给定 jdk1.5(或更高版本)并且您的连接是线程安全的,您应该使用 StringBuilder 而不是 StringBuffer http://java4ever.blogspot.com/2007/03/string-vs-stringbuffer-vs-stringbuilder.html 至于速度上的增益: http://www.about280.com/stringtest.html
就我个人而言,我会编写代码以提高可读性,因此除非您发现字符串连接会使您的代码变得相当慢,否则请使用使您的代码更具可读性的任何方法。
【讨论】:
StringBuffer 类维护一个字符数组来保存您连接的字符串的内容,而 + 方法每次调用时都会创建一个新字符串并附加两个参数(param1 + param2)。
StringBuffer 更快,因为 1. 它可能能够使用其现有的数组来连接/存储所有字符串。 2. 即使它们不适合数组,分配更大的后备数组比为每次调用生成新的 String 对象更快。
【讨论】:
现在,几乎在所有情况下,最好使用 StringBuilder(它是一个非同步版本;你什么时候并行构建字符串?),但会发生以下情况:
当你对两个字符串使用 + 时,它会编译如下代码:
String third = first + second;
这样的:
StringBuilder builder = new StringBuilder( first );
builder.append( second );
third = builder.toString();
因此,对于一些小例子来说,它通常没有什么区别。但是,当您构建一个复杂的字符串时,您通常需要处理的事情远不止这些;例如,您可能正在使用许多不同的附加语句,或者像这样的循环:
for( String str : strings ) {
out += str;
}
在这种情况下,每次迭代都需要一个新的 StringBuilder 实例和一个新的 String(out 的新值 - Strings 是不可变的)。这是非常浪费的。用单个 StringBuilder 替换它意味着您可以只生成一个 String 而不会用您不关心的 Strings 填充堆。
【讨论】:
要使用“+”连接两个字符串,需要为两个字符串分配空间,然后从两个字符串复制数据。 StringBuffer 针对连接进行了优化,分配的空间比最初需要的多。连接新字符串时,大多数情况下,只需将字符复制到现有字符串缓冲区的末尾即可。
对于连接两个字符串,'+' 运算符的开销可能会更少,但是当你连接更多的字符串时,StringBuffer 会领先,使用更少的内存分配和更少的数据复制。
【讨论】:
Java 将 string1 + string2 转换为 StringBuffer 构造、append() 和 toString()。这是有道理的。
但是,在 Java 1.4 和更早版本中,它会为语句单独中的每个 + 运算符执行此操作。这意味着执行 a + b + c 将导致 two 带有 two toString() 调用的 StringBuffer 构造。如果你有一长串 concats,它会变成一团糟。自己做意味着你可以控制它并正确地做。
Java 5.0 及更高版本似乎更明智地执行此操作,因此问题更小,当然也更简洁。
【讨论】:
StringBuffer 是可变的。它将字符串的值添加到 same 对象而不实例化另一个对象。做类似的事情:
myString = myString + "XYZ"
将创建一个 new 字符串对象。
【讨论】:
因为字符串在 Java 中是不可变的,所以每次连接字符串时,都会在内存中创建新对象。 SpringBuffer 在内存中使用相同的对象。
【讨论】:
在底层,它实际上创建并附加到一个 StringBuffer,在结果上调用 toString()。所以实际上你使用哪一个都没有关系了。
所以
String s = "a" + "b" + "c";
变成
String s = new StringBuffer().append("a").append("b").append("c").toString();
对于单个语句中的一堆内联追加来说确实如此。如果您在多个语句的过程中构建字符串,那么您就是在浪费内存,而 StringBuffer 或 StringBuilder 是您更好的选择。
【讨论】: