59

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

4

19 回答 19

64

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

当您将 + 与两个字符串一起使用时,它会编译如下代码:

String third = first + second;

对于这样的事情:

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

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

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

在这种情况下,每次迭代都需要一个新的StringBuilder实例和一个新的( -sString的新值是不可变的)。这是非常浪费的。用一个来替换它意味着你可以只生产一个,而不是用你不关心的 s来填充堆。outStringStringBuilderStringString

于 2008-09-15T19:02:48.177 回答
43

对于简单的连接,例如:

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稍微重一些,因为它的所有方法都是同步的。

于 2008-09-15T19:18:30.790 回答
21

一个不应该比另一个快。在 Java 1.4.2 之前情况并非如此,因为当使用“+”运算符连接两个以上的字符串时,String会在构建最终字符串的过程中创建中间对象。

但是,正如StringBuffer 的 JavaDoc所述,至少从使用“+”运算符的 Java 1.4.2 开始编译为创建 aStringBufferappend()ing 到它的许多字符串。所以显然没有区别。

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

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()将它们全部连接起来更干净。

于 2008-09-15T18:57:07.473 回答
10

在后台,它实际上创建并附加到一个 StringBuffer,在结果上调用 toString()。因此,实际上您不再使用哪个并不重要。

所以

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

变成

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

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

于 2008-09-15T18:57:09.377 回答
7

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

就我个人而言,我会编写代码以提高可读性,因此除非您发现字符串连接会使您的代码变得相当慢,否则请使用使您的代码更具可读性的任何方法。

于 2008-09-15T19:04:14.333 回答
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. ... 等等

如您所见,每次迭代都必须再复制一个字符,导致我们在每个循环中执行 1+2+3+4+5+...+N 次操作。这是一个 O(n^2) 操作。但是,如果我们事先知道我们只需要 N 个字符,我们可以在一次分配中完成,只复制我们正在使用的字符串中的 N 个字符 - 只需 O(n) 操作。

StringBuffer/StringBuilder 避免了这种情况,因为它们是可变的,因此不需要一遍又一遍地复制相同的数据(只要在它们的内部缓冲区中有空间可以复制)。他们避免执行与附加数量成正比的分配和复制,方法是按照其当前大小的比例过度分配缓冲区,从而提供摊销的 O(1) 附加。

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

于 2008-09-15T19:28:38.567 回答
3

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

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

正如在循环中使用 + 所指出的那样,涉及复制。

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

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

已编译为:

String foo = "abc"; 
于 2008-09-15T19:18:54.550 回答
3

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

但是,在 Java 1.4 和更早版本中,它会为语句中的每个+ 运算符单独执行此操作。这意味着执行 a + b + c 将导致两个带有两个toString() 调用的StringBuffer 构造。如果你有一长串 concats,它会变成一团糟。自己做意味着你可以控制它并正确地做到这一点。

Java 5.0 及更高版本似乎更明智地做到了这一点,因此问题更小,当然也更少冗长。

于 2008-09-15T19:01:41.923 回答
2

更多信息:

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

于 2009-07-21T01:48:39.910 回答
2

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

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

于 2008-09-15T19:03:11.810 回答
1

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

myString = myString + "XYZ"

将创建一个的String 对象。

于 2008-09-15T18:57:39.047 回答
1

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

于 2008-09-15T19:02:11.470 回答
1

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

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

于 2008-09-15T19:04:15.100 回答
1

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

String x = new String("something"); // 或者

字符串 x = "某事";

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

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

于 2008-09-15T19:04:56.843 回答
1

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

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

于 2008-09-15T19:08:12.280 回答
1

原因是字符串不可变。它不是修改字符串,而是创建一个新字符串。字符串池存储所有字符串值,直到垃圾收集器对其进行处理。想想有两个字符串作为Hellohow are you。如果我们考虑字符串池,它有两个字符串。

在此处输入图像描述

如果您尝试将这两个字符串连接为,

字符串 1 = 字符串 1+字符串 2

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

在此处输入图像描述

如果我们尝试连接数千个单词,它会获得更多内存。解决方案是 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);
   }
}

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

于 2019-11-10T20:58:40.990 回答
0

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

于 2008-09-15T18:57:27.877 回答
0

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

如果您真的想了解所有底层知识,您可以随时查看源代码:

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

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

于 2008-09-15T19:04:28.407 回答
0

Java 语言规范的字符串连接运算符 +部分为您提供了更多关于 + 运算符为何如此缓慢的背景信息。

于 2008-09-15T19:06:39.290 回答