繁体   English   中英

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

[英]Why to use StringBuffer in Java instead of the string concatenation operator

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

现在,几乎在所有情况下,最好使用 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而不会用您不关心的String填充堆。

对于简单的连接,例如:

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

在此循环中使用字符串会在 memory 中生成 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 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();

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

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

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

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

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

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

  1. 构造一个长度为 1 且值为“x”的字符串 object
  2. 创建一个大小为 2 的新字符串 object,将旧字符串“x”复制到其中,在 position 2 中添加“x”。
  3. 创建一个大小为 3 的新字符串 object,将旧字符串“xx”复制到其中,在 position 3 中添加“x”。
  4. ... 等等

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

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

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

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

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

Java 5.0 及更高版本似乎做得更明智,因此问题较少,而且肯定不那么冗长。

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

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

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

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

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

已编译为:

String foo = "abc"; 

更多信息:

StringBuffer 是一个线程安全的 class


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

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

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

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

在此处输入图像描述

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

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

现在创建一个新的字符串 object 并将其存储在字符串池中。

在此处输入图像描述

如果我们尝试连接数千个单词,它会得到更多的 memory。 解决方案是 StringBuilder 或 StringBuffer。 只能创建一个 Object 并且可以修改。 因为两者都是可变的。那么不需要更多的 memory。 如果您认为线程安全,则使用 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);
   }
}

output:私有默认受保护公共

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

myString = myString + "XYZ"

将创建一个的字符串 object。

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

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

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

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

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

字符串 x = "某事";

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

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

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

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

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

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM