简体   繁体   English

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

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

Someone told me it's more efficient to use StringBuffer to concatenate strings in Java than to use the + operator for String s.有人告诉我,在 Java 中使用StringBuffer连接字符串比对String使用+运算符更有效。 What happens under the hood when you do that?当你这样做时,引擎盖下会发生什么? What does StringBuffer do differently? StringBuffer有什么不同?

It's better to use StringBuilder (it's an unsynchronized version; when do you build strings in parallel?) these days, in almost every case, but here's what happens:现在,几乎在所有情况下,最好使用 StringBuilder (它是一个不同步的版本;你什么时候并行构建字符串?),但会发生以下情况:

When you use + with two strings, it compiles code like this:当您将 + 与两个字符串一起使用时,它会编译如下代码:

String third = first + second;

To something like this:对于这样的事情:

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

Therefore for just little examples, it usually doesn't make a difference.因此,对于一些小例子,它通常没有什么区别。 But when you're building a complex string, you've often got a lot more to deal with than this;但是,当您构建一个复杂的字符串时,您通常需要处理的事情远不止这些; for example, you might be using many different appending statements, or a loop like this:例如,您可能正在使用许多不同的附加语句,或者像这样的循环:

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

In this case, a new StringBuilder instance, and a new String (the new value of out - String s are immutable) is required in each iteration.在这种情况下,每次迭代都需要一个新的StringBuilder实例和一个新的Stringout - String的新值是不可变的)。 This is very wasteful.这是非常浪费的。 Replacing this with a single StringBuilder means you can just produce a single String and not fill up the heap with String s you don't care about.用单个StringBuilder替换它意味着您可以只生成一个String而不会用您不关心的String填充堆。

For simple concatenations like:对于简单的连接,例如:

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

It is rather pointless to use StringBuffer - as jodonnell pointed out it will be smartly translated into:使用StringBuffer是毫无意义的——正如jodonnell指出的那样,它将被巧妙地翻译成:

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

BUT it is very unperformant to concatenate strings in a loop, like:但是在循环中连接字符串是非常糟糕的,例如:

String s = "";
for (int i = 0; i < 10; i++) {
    s = s + Integer.toString(i);
}

Using string in this loop will generate 10 intermediate string objects in memory: "0", "01", "012" and so on.在此循环中使用字符串会在 memory 中生成 10 个中间字符串对象:“0”、“01”、“012”等。 While writing the same using StringBuffer you simply update some internal buffer of StringBuffer and you do not create those intermediate string objects that you do not need:在使用StringBuffer编写相同的内容时,您只需更新StringBuffer的一些内部缓冲区,并且您不需要创建那些不需要的中间字符串对象:

StringBuffer sb = new StringBuffer();
for (int i = 0; i < 10; i++) {
    sb.append(i);
}

Actually for the example above you should use StringBuilder (introduced in Java 1.5) instead of StringBuffer - StringBuffer is little heavier as all its methods are synchronized.实际上,对于上面的示例,您应该使用StringBuilder (在 Java 1.5 中引入)而不是StringBuffer - StringBuffer稍微重一些,因为它的所有方法都是同步的。

One shouldn't be faster than the other.一个不应该比另一个快。 This wasn't true before Java 1.4.2, because when concatenating more than two strings using the "+" operator, intermediate String objects would be created during the process of building the final string.在 Java 1.4.2 之前,情况并非如此,因为当使用“+”运算符连接两个以上的字符串时,会在构建最终字符串的过程中创建中间String对象。

However, as the JavaDoc for StringBuffer states, at least since Java 1.4.2 using the "+" operator compiles down to creating a StringBuffer and append() ing the many strings to it.然而,正如StringBuffer 的 JavaDoc所述,至少从 Java 1.4.2 开始,使用“+”运算符编译为创建一个StringBuffer append()许多字符串添加到它。 So no difference, apparently.所以显然没有区别。

However, be careful when using adding a string to another inside a loop: For example:但是,在循环中使用将字符串添加到另一个字符串时要小心:例如:

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

Keep in mind, however, that usually concatenating a few strings with "+" is cleaner than append() ing them all.但是请记住,通常用“+”连接几个字符串比append()更干净。

Under the hood, it actually creates and appends to a StringBuffer, calling toString() on the result.在后台,它实际上创建并附加到一个 StringBuffer,在结果上调用 toString()。 So it actually doesn't matter which you use anymore.因此,实际上您不再使用哪个并不重要。

So所以

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

becomes变成

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

That's true for a bunch of inlined appends within a single statement.这对于单个语句中的一堆内联追加是正确的。 If you build your string over the course of multiple statements, then you're wasting memory and a StringBuffer or StringBuilder is your better choice.如果您在多个语句的过程中构建字符串,那么您就是在浪费 memory,而 StringBuffer 或 StringBuilder 是您更好的选择。

I think that given jdk1.5 (or greater) and your concatenation is thread-safe you should use StringBuilder instead of StringBuffer http://java4ever.blogspot.com/2007/03/string-vs-stringbuffer-vs-stringbuilder.html As for the gains in speed: http://www.about280.com/stringtest.html我认为给定 jdk1.5(或更高版本)并且您的连接是线程安全的,您应该使用 StringBuilder 而不是 StringBuffer http://java4ever.blogspot.com/2007/03/string-vs-stringbuffer-vs-stringbuilder.ZFC35FDC70D5FC69D2698Z83A822C7A53E至于速度上的提升: http://www.about280.com/stringtest.html

Personally I'd code for readability, so unless you find that string concatenation makes your code considerably slower, stay with whichever method makes your code more readable.就我个人而言,我会编写代码以提高可读性,因此除非您发现字符串连接会使您的代码变得相当慢,否则请使用使您的代码更具可读性的任何方法。

In some cases this is obsolete due to optimisations performed by the compiler, but the general issue is that code like:在某些情况下,由于编译器执行的优化,这已过时,但一般问题是代码如下:

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

will act as below (each step being the next loop iteration):将如下所示(每个步骤都是下一个循环迭代):

  1. construct a string object of length 1, and value "x"构造一个长度为 1 且值为“x”的字符串 object
  2. Create a new string object of size 2, copy the old string "x" into it, add "x" in position 2.创建一个大小为 2 的新字符串 object,将旧字符串“x”复制到其中,在 position 2 中添加“x”。
  3. Create a new string object of size 3, copy the old string "xx" into it, add "x" in position 3.创建一个大小为 3 的新字符串 object,将旧字符串“xx”复制到其中,在 position 3 中添加“x”。
  4. ... and so on ... 等等

As you can see, each iteration is having to copy one more character, resulting in us performing 1+2+3+4+5+...+N operations each loop.如您所见,每次迭代都必须再复制一个字符,导致我们在每个循环中执行 1+2+3+4+5+...+N 次操作。 This is an O(n^2) operation.这是一个 O(n^2) 操作。 If however we knew in advance that we only needed N characters, we could do it in a single allocation, with copy of just N characters from the strings we were using - a mere O(n) operation.但是,如果我们事先知道我们只需要 N 个字符,我们可以在一次分配中完成,只复制我们正在使用的字符串中的 N 个字符 - 只需 O(n) 操作。

StringBuffer/StringBuilder avoid this because they are mutable, and so do not need to keep copying the same data over and over (so long as there is space to copy into in their internal buffer). StringBuffer/StringBuilder 避免了这种情况,因为它们是可变的,因此不需要一遍又一遍地复制相同的数据(只要在它们的内部缓冲区中有空间可以复制)。 They avoid performing an allocation and copy proportional to the number of appends done by over-allocing their buffer by a proportion of its current size, giving amortized O(1) appending.他们避免执行与附加数量成正比的分配和复制,方法是按照其当前大小的比例过度分配缓冲区,从而提供摊销的 O(1) 附加。

However its worth noting that often the compiler will be able to optimise code into StringBuilder style (or better - since it can perform constant folding etc.) automatically.然而值得注意的是,编译器通常能够自动将代码优化为 StringBuilder 样式(或者更好——因为它可以执行常量折叠等)。

Java turns string1 + string2 into a StringBuffer construct, append(), and toString(). Java 将 string1 + string2 转换为 StringBuffer 构造、append() 和 toString()。 This makes sense.这是有道理的。

However, in Java 1.4 and earlier, it would do this for each + operator in the statement separately .但是,在 Java 1.4 及更早版本中,它将分别为语句中的每个+ 运算符执行此操作。 This meant that doing a + b + c would result in two StringBuffer constructs with two toString() calls.这意味着执行 a + b + c 将导致两个带有两个toString() 调用的 StringBuffer 构造。 If you had a long string of concats, it would turn into a real mess.如果你有一长串 concats,它会变成一团糟。 Doing it yourself meant you could control this and do it properly.自己做意味着你可以控制它并正确地做到这一点。

Java 5.0 and above seem to do it more sensibly, so it's less of a problem and is certainly less verbose. Java 5.0 及更高版本似乎做得更明智,因此问题较少,而且肯定不那么冗长。

AFAIK it depends on version of JVM, in versions prior to 1.5 using "+" or "+=" actually copied the whole string every time. AFAIK 它取决于 JVM 的版本,在 1.5 之前的版本中,使用“+”或“+=”实际上每次都复制整个字符串。

Beware that using += actually allocates the new copy of string.请注意,使用 += 实际上会分配字符串的新副本。

As was pointed using + in loops involves copying.正如在循环中使用 + 所指出的那样,涉及复制。

When strings that are conactenated are compile time constants there concatenated at compile time, so当连接的字符串是编译时常量时,在编译时连接,所以

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

Has is compiled to:已编译为:

String foo = "abc"; 

Further information:更多信息:

StringBuffer is a thread-safe class StringBuffer 是一个线程安全的 class


public final class StringBuffer extends AbstractStringBuilder
    implements Serializable, CharSequence
{
// .. skip ..
     public synchronized StringBuffer append(StringBuffer stringbuffer)
    {
        super.append(stringbuffer);
        return this;
    }
// .. skip ..
}

But StringBuilder is not thread-safe, thus it is faster to use StringBuilder if possible但是 StringBuilder 不是线程安全的,因此如果可能的话使用 StringBuilder 会更快


public final class StringBuilder extends AbstractStringBuilder
    implements Serializable, CharSequence
{
// .. skip ..
    public StringBuilder append(String s)
    {
        super.append(s);
        return this;
    }
// .. skip ..
}

The StringBuffer class maintains an array of characters to hold the contents of the strings you concatenate, whereas the + method creates a new string each time its called and appends the two parameters (param1 + param2). StringBuffer class 维护一个字符数组来保存您连接的字符串的内容,而 + 方法每次调用时都会创建一个新字符串并附加两个参数(param1 + param2)。

The StringBuffer is faster because 1. it might be able to use its already existing array to concat/store all of the strings. StringBuffer 更快,因为 1. 它可能能够使用其已经存在的数组来连接/存储所有字符串。 2. even if they don't fit in the array, its faster to allocate a larger backing array then to generate new String objects for each evocation. 2. 即使它们不适合数组,分配更大的后备数组比为每次调用生成新的 String 对象更快。

The reason is the String immutable.原因是字符串不可变。 Instead of modifying a string, It creates a new one.它不是修改字符串,而是创建一个新字符串。 String pool stores all String values until garbage collectors plush it.字符串池存储所有字符串值,直到垃圾收集器对其进行处理。 Think about two strings are there as Hello and how are you .想想有两个字符串作为Hellohow are you If we consider the String pool, It has two String.如果我们考虑字符串池,它有两个字符串。

在此处输入图像描述

If you try to concatenate these two string as,如果您尝试将这两个字符串连接为,

string1 = string1+string2字符串 1 = 字符串 1+字符串 2

Now create a new String object and store it in the String pool.现在创建一个新的字符串 object 并将其存储在字符串池中。

在此处输入图像描述

If we try to concatenate thousand of words it's getting more memory.如果我们尝试连接数千个单词,它会得到更多的 memory。 The Solution for this is StringBuilder or StringBuffer.解决方案是 StringBuilder 或 StringBuffer。 It can be created only one Object and can be modified.只能创建一个 Object 并且可以修改。 Because both are mutable.Then no need more memory.因为两者都是可变的。那么不需要更多的 memory。 If you consider thread-safe then use StringBuffer, Otherwise StringBuilder.如果您认为线程安全,则使用 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: private default protected public output:私有默认受保护公共

StringBuffer is mutable. StringBuffer 是可变的。 It adds the value of the string to the same object without instantiating another object.它将字符串的值添加到同一个object 而不实例化另一个 object。 Doing something like:做类似的事情:

myString = myString + "XYZ"

will create a new String object.将创建一个的字符串 object。

To concatenate two strings using '+', a new string needs to be allocated with space for both strings, and then the data copied over from both strings.要使用“+”连接两个字符串,需要为两个字符串分配空间,然后从两个字符串复制数据。 A StringBuffer is optimized for concatenating, and allocates more space than needed initially. StringBuffer 针对连接进行了优化,并分配了比最初所需更多的空间。 When you concatenate a new string, in most cases, the characters can simply be copied to the end of the existing string buffer.在连接新字符串时,在大多数情况下,字符可以简单地复制到现有字符串缓冲区的末尾。
For concatenating two strings, the '+' operator will probably have less overhead, but as you concatenate more strings, the StringBuffer will come out ahead, using fewer memory allocations, and less copying of data.对于连接两个字符串,'+' 运算符的开销可能会更少,但是当你连接更多的字符串时,StringBuffer 会领先,使用更少的 memory 分配和更少的数据复制。

Because Strings are immutable, each call to the + operator creates a new String object and copies the String data over to the new String.因为字符串是不可变的,所以每次调用 + 运算符都会创建一个新字符串 object 并将字符串数据复制到新字符串。 Since copying a String takes time linear in the length of the String, a sequence of N calls to the + operator results in O(N 2 ) running time (quadratic).由于复制字符串所需的时间与字符串的长度呈线性关系,因此对 + 运算符的 N 次调用序列会导致 O(N 2 ) 运行时间(二次)。

Conversely, since a StringBuffer is mutable, it does not need to copy the String every time you perform an Append(), so a sequence of N Append() calls takes O(N) time (linear).相反,由于 StringBuffer 是可变的,它不需要每次执行 Append() 时都复制 String,因此 N Append() 调用的序列需要 O(N) 时间(线性)。 This only makes a significant difference in runtime if you are appending a large number of Strings together.如果您将大量字符串附加在一起,这只会在运行时产生显着差异。

As said, the String object is ummutable, meaning once it is created (see below) it cannot be changed.如前所述,字符串 object 是不可变的,这意味着一旦创建(见下文)就无法更改。

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

String x = "something";字符串 x = "某事";

So when you attempt to concanate String objects, the value of those objects are taken and put into a new String object.因此,当您尝试连接 String 对象时,会获取这些对象的值并将其放入新的 String object。

If you instead use the StringBuffer, which IS mutable, you continually add the values to an internal list of char (primitives), which can be extended or truncated to fit the value needed.如果您改为使用可变的 StringBuffer,您会不断地将值添加到 char(基元)的内部列表中,该列表可以扩展或截断以适应所需的值。 No new objects are created, only new char's are created/removed when needed to hold the values.不创建新对象,仅在需要保存值时创建/删除新字符。

When you concatenate two strings, you actually create a third String object in Java.连接两个字符串时,实际上是在 Java 中创建了第三个字符串 object。 Using StringBuffer (or StringBuilder in Java 5/6), is faster because it uses an internal array of chars to store the string, and when you use one of its add(...) methods, it doesn't create a new String object.使用 StringBuffer(或 Java 5/6 中的 StringBuilder)更快,因为它使用内部字符数组来存储字符串,并且当您使用其中一个 add(...) 方法时,它不会创建新的 String object。 Instead, StringBuffer/Buider appends the internal array.相反,StringBuffer/Buider 附加了内部数组。

In simple concatenations, it's not really an issue whether you concatenate strings using StringBuffer/Builder or the '+' operator, but when doing a lot of string concatenations, you'll see that using a StringBuffer/Builder is way faster.在简单的连接中,使用 StringBuffer/Builder 或 '+' 运算符连接字符串并不是真正的问题,但是在进行大量字符串连接时,您会发现使用 StringBuffer/Builder 更快。

Because Strings are imutable in Java, every time you concanate a String, new object is created in memory.因为字符串在 Java 中是不可变的,所以每次连接字符串时,都会在 memory 中创建新的 object。 SpringBuffer use the same object in memory. SpringBuffer 在 memory 中使用相同的 object。

I think the simplest answer is: it's faster.我认为最简单的答案是:它更快。

If you really want to know all the under-the-hood stuff, you could always have a look at the source yourself:如果您真的想了解所有底层知识,您可以随时查看源代码:

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

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

The section String Concatenation Operator + of the Java Language Specification gives you some more background information on why the + operator can be so slow. Java 语言规范的字符串连接运算符 +部分为您提供了有关 + 运算符为何如此缓慢的更多背景信息。

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

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