为什么在Java中使用StringBuffer而不是字符串连接运算符

时间:2008-09-15 18:55:48

标签: java optimization string-concatenation

有人告诉我,使用StringBuffer连接Java中的字符串比使用+ String运算符更有效率。当你这样做时会发生什么? StringBuffer做什么不同?

19 个答案:

答案 0 :(得分:61)

最好使用StringBuilder(它是一个不同步的版本;你什么时候并行构建字符串?)这几天,几乎在所有情况下,但这是发生的事情:

当你对两个字符串使用+时,它会编译如下代码:

String third = first + second;

对于这样的事情:

StringBuilder builder = new StringBuilder( first );
builder.append( second );
third = builder.toString();

因此,仅举几个例子,它通常没有什么区别。但是当你构建一个复杂的字符串时,你经常需要处理的事情比这更多;例如,您可能正在使用许多不同的附加语句,或者像这样的循环:

for( String str : strings ) {
  out += str;
}

在这种情况下,每次迭代都需要一个新的StringBuilder实例和一个新的Stringout - String的新值是不可变的)。这非常浪费。用单个StringBuilder替换它意味着您只需生成一个String而不用{0}你不关心的堆填充堆。

答案 1 :(得分:42)

对于简单的连接,例如:

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);
}

在此循环中使用string将在内存中生成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稍微重一点,因为它的所有方法都是同步的。

答案 2 :(得分:20)

一个人不应该比另一个人快。在Java 1.4.2之前不是这样,因为当使用“+”运算符连接两个以上的字符串时,将在构建最终字符串的过程中创建中间String个对象。

然而,正如JavaDoc for StringBuffer所述,至少从使用“+”运算符的Java 1.4.2开始编译为向其创建StringBufferappend()多个字符串。所以没有区别,显然。

但是,在循环中使用向另一个添加字符串时要小心!例如:

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()更加清晰。

答案 3 :(得分:9)

在引擎盖下,它实际上创建并附加到StringBuffer,在结果上调用toString()。所以你使用它实际上并不重要。

所以

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

变为

String s = new StringBuffer().append("a").append("b").append("c").toString();

对于在单个语句中的一堆内联追加,这是正确的。如果你在多个语句的过程中构建字符串,那么你就是在浪费内存,而StringBuffer或StringBuilder是你更好的选择。

答案 4 :(得分:7)

我认为给定jdk1.5(或更高版本)并且你的连接是线程安全的,你应该使用StringBuilder而不是StringBuffer http://java4ever.blogspot.com/2007/03/string-vs-stringbuffer-vs-stringbuilder.html 至于速度的提升: http://www.about280.com/stringtest.html

就我个人而言,我的代码是为了便于阅读,所以除非你发现字符串连接使你的代码相当慢,否则请使用哪种方法使代码更具可读性。

答案 5 :(得分:5)

在某些情况下,由于编译器执行了优化,这已经过时,但一般问题是代码如下:

string myString="";
for(int i=0;i<x;i++)
{
    myString += "x";
}

将如下所示(每一步都是下一个循环迭代):

  1. 构造一个长度为1的字符串对象,值为“x”
  2. 创建一个大小为2的新字符串对象,将旧字符串“x”复制到其中,在位置2中添加“x”。
  3. 创建一个大小为3的新字符串对象,将旧字符串“xx”复制到其中,在位置3中添加“x”。
  4. ......等等
  5. 如您所见,每次迭代都要复制一个字符,导致我们在每个循环中执行1 + 2 + 3 + 4 + 5 + ... + N个操作。这是O(n ^ 2)操作。但是,如果我们事先知道我们只需要N个字符,我们可以在一次分配中完成,只使用我们使用的字符串中只有N个字符的副本 - 仅仅是O(n)操作。

    StringBuffer / StringBuilder避免这种情况,因为它们是可变的,因此不需要反复复制相同的数据(只要有空间可以复制到其内部缓冲区中)。他们避免执行分配和复制,与按当前大小的一部分过度分配缓冲区所做的附加次数成比例,给予摊销的O(1)追加。

    然而值得注意的是,编译器通常会自动将代码优化为StringBuilder样式(或更好 - 因为它可以执行常量折叠等)。

答案 6 :(得分:3)

Java将string1 + string2转换为StringBuffer构造,append()和toString()。这是有道理的。

但是,在Java 1.4及更早版本中,它将在语句中单独每个 +运算符执行此操作。这意味着执行+ b + c将导致两个 StringBuffer构造与两个 toString()调用。如果你有一长串的concats,它将变成一个真正的混乱。自己动手意味着你可以控制它并正确地完成它。

Java 5.0及更高版本似乎更明智地做到了,所以它不是一个问题,而且肯定不那么冗长。

答案 7 :(得分:3)

AFAIK它依赖于JVM的版本,在1.5之前的版本中使用“+”或“+ =”实际上每次都复制了整个字符串。

注意使用+ =实际分配新的字符串副本。

正如使用+ in循环指向复制。

当被连接的字符串是编译时常量时,它们在编译时连接,所以

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

已编译为:

String foo = "abc"; 

答案 8 :(得分:1)

因为字符串是不可变的,所以每次调用+运算符都会创建一个新的String对象,并将String数据复制到新的String。由于复制String在String的长度上需要线性时间,因此对+运算符的N个调用序列会导致O(N 2 )运行时间(二次)。

相反,由于StringBuffer是可变的,因此每次执行Append()时都不需要复制String,因此N Append()调用序列需要O(N)时间(线性)。如果要将大量字符串附加在一起,这只会在运行时产生显着差异。

答案 9 :(得分:1)

更多信息:

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 ..
}

答案 10 :(得分:1)

当您连接两个字符串时,实际上是在Java中创建第三个String对象。使用StringBuffer(或Java 5/6中的StringBuilder)更快,因为它使用内部字符数组来存储字符串,当你使用其中一个add(...)方法时,它不会创建新的字符串宾语。而是,StringBuffer / Buider附加内部数组。

在简单的连接中,使用StringBuffer / Builder或'+'运算符连接字符串并不是一个真正的问题,但是在进行大量字符串连接时,您会发现使用StringBuffer / Builder更快。

答案 11 :(得分:1)

如上所述,String对象是不可变的,这意味着一旦创建它(见下文)就无法更改。

  

String x = new String(“something”); //或

     

字符串x =“某事”;

因此,当您尝试连接String对象时,将获取这些对象的值并将其放入新的String对象中。

如果您改为使用可变的StringBuffer,则不断将值添加到char(基元)的内部列表中,可以对其进行扩展或截断以适应所需的值。没有创建新对象,只需要在保存值时创建/删除新的char。

答案 12 :(得分:1)

StringBuffer类维护一个字符数组来保存你连接的字符串的内容,而+方法每次调用时都会创建一个新字符串并附加两个参数(param1 + param2)。

StringBuffer更快,因为1.它可能能够使用其现有的数组来连接/存储所有字符串。 2.即使它们不适合数组,也可以更快地分配更大的后备数组,然后为每次调用生成新的String对象。

答案 13 :(得分:1)

要使用'+'连接两个字符串,需要为两个字符串分配一个新字符串,然后从两个字符串复制数据。 StringBuffer针对连接进行了优化,并且最初分配的空间超出了所需的空间。连接新字符串时,在大多数情况下,可以简单地将字符复制到现有字符串缓冲区的末尾 为了连接两个字符串,'+'运算符可能会有更少的开销,但是当你连接更多的字符串时,StringBuffer将提前出现,使用更少的内存分配,减少数据复制。

答案 14 :(得分:1)

StringBuffer是可变的。它将字符串的值添加到相同的对象,而不实例化另一个对象。做类似的事情:

myString = myString + "XYZ"

将创建一个 new String对象。

答案 15 :(得分:0)

Java语言规范的String Concatenation Operator +部分为您提供了有关+运算符为何如此慢的原因的更多背景信息。

答案 16 :(得分:0)

我认为最简单的答案是:它更快。

如果你真的想知道所有引擎盖下的内容,你可以随时查看来源:

http://www.sun.com/software/opensource/java/getinvolved.jsp

http://download.java.net/jdk6/latest/archive/

答案 17 :(得分:0)

因为字符串在Java中是不可变的,所以每次连接字符串时,都会在内存中创建新对象。 StringBuffer在内存中使用相同的对象。

答案 18 :(得分:0)

原因是字符串不可变。它创建一个新的字符串,而不是修改字符串。 字符串池存储所有字符串值,直到垃圾回收器将其值加长。  考虑一下两个字符串,分别为Hellohow are you。 如果我们考虑字符串池,它有两个字符串。

enter image description here

如果您尝试将这两个字符串串联在一起,

  

string1 =字符串1+字符串2

现在创建一个新的String对象并将其存储在String池中。

enter image description here

如果我们尝试连接上千个单词,它将获得更多的内存。解决方案是StringBuilder或StringBuffer。只能创建一个对象,并且可以对其进行修改。因为两者都是可变的,所以不需要更多的内存。如果您认为线程安全,则使用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);
   }
}
  

输出:私有默认受保护的公共