为什么在Java中使用StringBuffer代替字符串连接运算符
有人告诉我,在Java中使用StringBuffer连接字符串比在String中使用+运算符更有效。当我们这样做时,在幕后会发生什么? StringBuffer
有什么不同?
解决方案
回答
一个不应该比另一个更快。在Java 1.4.2之前,情况并非如此,因为使用" +"运算符连接两个以上的字符串时,将在构建最终字符串的过程中创建中间的" String"对象。
然而,正如StringBuffer的JavaDoc所指出的那样,至少从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()
将它们全部都干净。
回答
实际上,它实际上创建并追加到StringBuffer,并在结果上调用toString()。因此,实际上,再使用哪个都无关紧要。
所以
String s = "a" + "b" + "c";
变成
String s = new StringBuffer().append("a").append("b").append("c").toString();
对于单个语句中的一堆内联追加而言,这是正确的。如果我们在多个语句的过程中构建字符串,那么我们将浪费内存,而StringBuffer或者StringBuilder是更好的选择。
回答
由于字符串在Java中是不可枚举的,因此,每次合并字符串时,都会在内存中创建新对象。 SpringBuffer在内存中使用相同的对象。
回答
StringBuffer是可变的。它将字符串的值添加到同一对象,而无需实例化另一个对象。做类似的事情:
myString = myString + "XYZ"
将创建一个新的String对象。
回答
Java将string1 + string2转换为StringBuffer构造,append()和toString()。这是有道理的。
但是,在Java 1.4及更早版本中,它将对语句中的每个+运算符单独执行此操作。这意味着执行a + b + c将导致带有两个toString()调用的两个StringBuffer构造。如果我们有一连串的混乱,那将变成一团糟。自己做意味着我们可以控制它并正确地执行它。
Java 5.0及更高版本似乎更明智,所以它不成问题,而且冗长。
回答
要使用" +"连接两个字符串,需要为两个字符串分配一个新的字符串,并为其分配空间,然后再从两个字符串中复制数据。 StringBuffer已针对连接进行了优化,并分配了比最初所需更多的空间。在大多数情况下,连接新字符串时,可以将字符简单地复制到现有字符串缓冲区的末尾。
对于连接两个字符串," +"运算符可能会减少开销,但是当我们连接更多字符串时,StringBuffer将会领先,使用较少的内存分配和较少的数据复制。
回答
如今,在几乎所有情况下,都最好使用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字符串的新值)。这非常浪费。用单个StringBuilder代替它意味着我们可以只产生单个String,而不用不关心的String填充堆。
回答
StringBuffer类维护一个字符数组,以保存连接的字符串的内容,而+方法每次调用时都会创建一个新字符串,并添加两个参数(param1 + param2)。
StringBuffer之所以更快,是因为1.它可能能够使用其已经存在的数组来合并/存储所有字符串。 2.即使它们不适合数组,分配更大的支持数组然后为每次调用生成新的String对象的速度也更快。
回答
我认为给定jdk1.5(或者更高版本)并且连接是线程安全的,则应使用StringBuilder而不是StringBuffer
http://java4ever.blogspot.com/2007/03/string-vs-stringbuffer-vs-stringbuilder.html
至于速度的提高:
http://www.about280.com/stringtest.html
就个人而言,我会编写代码以提高可读性,因此,除非我们发现字符串串联会使代码变慢得多,否则无论使用哪种方法都可以使代码更具可读性。
回答
因为字符串是不可变的,所以每次对+运算符的调用都会创建一个新的String对象,并将String数据复制到新的String上。由于复制字符串花费的时间与字符串的长度成线性关系,因此对+运算符进行N次调用会导致O(N2)运行时间(二次)。
相反,由于StringBuffer是可变的,因此不需要每次执行Append()时都复制String,因此N个Append()调用序列需要O(N)时间(线性)。如果将大量字符串添加在一起,这只会在运行时产生重大差异。
回答
我认为最简单的答案是:更快。
如果我们真的想了解所有幕后知识,则可以随时查看源代码:
http://www.sun.com/software/opensource/java/getinvolved.jsp
http://download.java.net/jdk6/latest/archive/
回答
如前所述,String对象是不可变的,这意味着一旦创建(参见下文)就无法对其进行更改。
String x = new String("something"); // or String x = "something";
因此,当我们尝试合并String对象时,这些对象的值将被获取并放入新的String对象中。
如果改为使用可变的StringBuffer,则将值不断添加到内部char(基元)列表中,该列表可以扩展或者截断以适合所需的值。需要保留值时,不会创建新对象,仅会创建/删除新字符。
回答
Java语言规范的"字符串串联运算符+"部分为我们提供了更多有关+运算符为何如此慢的原因的背景信息。
回答
当连接两个字符串时,实际上是在Java中创建了第三个String对象。使用StringBuffer(或者Java 5/6中的StringBuilder),速度更快,因为它使用内部字符数组存储字符串,并且在使用其中的add(...)方法之一时,它不会创建新的String目的。而是,StringBuffer / Buider追加内部数组。
在简单的串联中,使用StringBuffer / Builder或者'+'运算符来串联字符串并不是一个真正的问题,但是在进行大量字符串串联时,我们会发现使用StringBuffer / Builder的速度更快。
回答
对于简单的串联,例如:
String s = "a" + "b" + "c";
正如jodonnell指出的那样,使用StringBuffer是毫无意义的,它将巧妙地转换为:
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稍微有点沉重。
回答
AFAIK取决于JVM的版本,在1.5之前的版本中,每次使用" +"或者" + ="都会实际复制整个字符串。
请注意,使用+ =实际上会分配新的字符串副本。
如前所述,使用+ in循环涉及复制。
当连接的字符串是编译时间常量时,它们在编译时连接在一起,因此
String foo = "a" + "b" + "c";
已编译为:
String foo = "abc";
回答
在某些情况下,由于编译器进行了优化,因此已经过时了,但是一般的问题是这样的代码:
string myString=""; for(int i=0;i<x;i++) { myString += "x"; }
将如下所示(每个步骤是下一个循环迭代):
- 构造一个长度为1且值为" x"的字符串对象
- 创建一个大小为2的新字符串对象,将旧字符串" x"复制到其中,并在位置2添加" x"。
- 创建一个大小为3的新字符串对象,将旧字符串" xx"复制到其中,并在位置3添加" x"。
- ... 等等
如我们所见,每次迭代都必须再复制一个字符,导致我们在每个循环中执行1 + 2 + 3 + 4 + 5 + ... + N个操作。这是O(n ^ 2)运算。但是,如果我们事先知道我们只需要N个字符,则可以在一次分配中完成它,只需使用O(n)操作即可从字符串中复制N个字符。
StringBuffer / StringBuilder避免这种情况,因为它们是可变的,因此不需要一遍又一遍地复制相同的数据(只要在其内部缓冲区中有复制的空间)。他们避免执行分配,并按正好通过按当前大小的一部分过度分配缓冲区来完成追加的数量来进行复制,从而得到摊销的O(1)追加。
但是,值得注意的是,编译器通常能够自动将代码优化为StringBuilder样式(或者更好,因为它可以执行恒定折叠等)。
回答
更多信息:
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 .. }