Зачем использовать StringBuffer в Java вместо оператора конкатенации строк

Кто-то сказал мне, что для объединения строк в Java более эффективно использовать StringBuffer, чем использовать оператор + для Strings. Что происходит под капотом, когда вы это делаете? Что StringBuffer делает по-другому?


person Community    schedule 15.09.2008    source источник


Ответы (19)


В наши дни лучше использовать 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 - Strings является неизменным). Это очень расточительно. Замена этого на один StringBuilder означает, что вы можете просто создать один String и не заполнять кучу String, которые вам не нужны.

person Calum    schedule 15.09.2008
comment
Почему компилятор не может это оптимизировать? Похоже, что компилятор должен это понять. - person Andrew Case; 31.10.2013

Для простых конкатенаций, например:

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 немного тяжелее, так как все его методы синхронизированы.

person tkokoszka    schedule 15.09.2008
comment
Я ожидал, что a + b + c на самом деле будет скомпилирован в abc напрямую, без конкатенации строк во время выполнения. - person Thilo; 08.05.2009
comment
@Thilo: Да, большинство компиляторов Java фактически объединяют конкатенации строковых литералов в один литерал. См., Например, nicklothian.com/blog/2005/06/09/on -java-строка-конкатенация - person sleske; 09.10.2009

Один не должен быть быстрее другого. Это было не так до Java 1.4.2, потому что при объединении более двух строк с использованием оператора «+» промежуточные объекты String создавались бы в процессе построения конечной строки.

Однако как JavaDoc для StringBuffer заявляет, по крайней мере, с тех пор, как 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().

person André Chalella    schedule 15.09.2008
comment
В конце вы говорите, что это дорого, но вы не указали на какой-либо другой способ сделать это, например, изменить оператор FOR ... - person user1156544; 29.03.2016
comment
@ user1156544 Как лучше объединить строки в массив? - person user3932000; 07.12.2016
comment
Вы можете избежать использования расширенного for, потому что, как сказал OP, он создает промежуточную строку для каждого цикла - person user1156544; 07.12.2016

Под капотом он фактически создает и добавляет к StringBuffer, вызывая toString () для результата. Так что на самом деле не имеет значения, что вы используете.

So

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

становится

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

Это верно для группы встроенных добавлений в одном операторе. Если вы создаете свою строку в ходе нескольких операторов, вы тратите память, и StringBuffer или StringBuilder - ваш лучший выбор.

person jodonnell    schedule 15.09.2008
comment
Вы уверены, что это не String x1 = StringBuffer (). Append (a) .toString (); Строка x2 = StringBuffer (x1) .append (b) .toString (); и так далее? - person Stroboskop; 30.07.2009
comment
Это неправда. Конкатенации строковых литералов будут фактически оптимизированы до одного литерала (по крайней мере, с помощью Sun javac, начиная с JDK 1.4.) StrinBuffer используется, если не все выражения являются строковыми литералами. - person sleske; 09.10.2009

Я думаю, что с учетом jdk1.5 (или выше) и вашей конкатенации поточно-ориентированной вы должны использовать StringBuilder вместо StringBuffer http://java4ever.blogspot.com/2007/03/string-vs-stringbuffer-vs-stringbuilder.html Что касается увеличения скорости: http://www.about280.com/stringtest.html

Лично я бы кодировал для удобочитаемости, поэтому, если вы не обнаружите, что конкатенация строк значительно замедляет ваш код, оставайтесь с тем методом, который делает ваш код более читаемым.

person slipset    schedule 15.09.2008

В некоторых случаях это устарело из-за оптимизации, выполненной компилятором, но общая проблема заключается в том, что код вроде:

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

будет действовать, как показано ниже (каждый шаг является следующей итерацией цикла):

  1. создать строковый объект длиной 1 и значением «x»
  2. Создайте новый строковый объект размером 2, скопируйте в него старую строку «x», добавьте «x» в позицию 2.
  3. Создайте новый строковый объект размером 3, скопируйте в него старую строку «xx», добавьте «x» в позицию 3.
  4. ... и так далее

Как видите, на каждой итерации необходимо копировать еще один символ, в результате чего мы выполняем 1 + 2 + 3 + 4 + 5 + ... + N операций в каждом цикле. Это операция O (n ^ 2). Однако если бы мы знали заранее, что нам нужно только N символов, мы могли бы сделать это за одно выделение, скопировав всего N символов из строк, которые мы использовали - простая операция O (n).

StringBuffer / StringBuilder избегают этого, потому что они изменяемы, и поэтому им не нужно копировать одни и те же данные снова и снова (пока есть место для копирования во внутреннем буфере). Они избегают выполнения выделения и копирования, пропорционального количеству выполненных добавлений, за счет чрезмерного выделения своего буфера на пропорцию его текущего размера, что дает амортизированное добавление O (1).

Однако стоит отметить, что часто компилятор может оптимизировать код в стиле StringBuilder (или лучше - поскольку он может выполнять сворачивание констант и т. Д.) Автоматически.

person Brian    schedule 15.09.2008

Java превращает string1 + string2 в конструкцию StringBuffer, append () и toString (). Это имеет смысл.

Однако в Java 1.4 и более ранних версиях это будет сделано для каждого + оператора в операторе отдельно. Это означало, что выполнение a + b + c приведет к двум конструкциям StringBuffer с двумя вызовами toString (). Если бы у вас была длинная цепочка конкатов, это превратилось бы в настоящий беспорядок. Делая это самостоятельно, вы могли контролировать это и делать это должным образом.

Java 5.0 и выше, кажется, делают это более разумно, поэтому это меньше проблем и, конечно, менее многословно.

person Alan Krueger    schedule 15.09.2008

AFAIK это зависит от версии JVM, в версиях до 1.5 использование «+» или «+ =» фактически каждый раз копировало всю строку.

Помните, что использование + = фактически выделяет новую копию строки.

Как было указано, использование + in циклов подразумевает копирование.

Когда строки, которые объединяются, являются константами времени компиляции, они объединяются во время компиляции, поэтому

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

Собран для:

String foo = "abc"; 
person jb.    schedule 15.09.2008

Класс StringBuffer поддерживает массив символов для хранения содержимого строк, которые вы объединяете, тогда как метод + создает новую строку при каждом вызове и добавляет два параметра (param1 + param2).

StringBuffer работает быстрее, потому что 1. он может использовать свой уже существующий массив для объединения / хранения всех строк. 2. даже если они не помещаются в массив, быстрее выделить больший резервный массив, чем создавать новые объекты String для каждого вызова.

person Community    schedule 15.09.2008

Дальнейшая информация:

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

person Eric Yung    schedule 21.07.2009
comment
Хотя информация здесь верна, она не отвечает на заданный вопрос! Так что не следует добавлять в качестве ответа! - person Anurag Kapur; 30.04.2014

StringBuffer изменяемый. Он добавляет значение строки к тому же объекту без создания экземпляра другого объекта. Сделать что-то вроде:

myString = myString + "XYZ"

создаст новый объект String.

person Loren Segal    schedule 15.09.2008

Чтобы объединить две строки с помощью '+', необходимо выделить новую строку с пространством для обеих строк, а затем данные скопировать из обеих строк. StringBuffer оптимизирован для объединения и выделяет больше места, чем необходимо изначально. Когда вы объединяете новую строку, в большинстве случаев символы можно просто скопировать в конец существующего строкового буфера.
Для объединения двух строк оператор '+', вероятно, будет иметь меньше накладных расходов, но по мере объединения больше strings, StringBuffer выйдет вперед, используя меньше выделений памяти и меньше копий данных.

person Eclipse    schedule 15.09.2008

Поскольку строки неизменяемы, каждый вызов оператора + создает новый объект String и копирует данные String в новую строку. Поскольку копирование String занимает время, линейное по длине String, последовательность из N вызовов оператора + приводит к времени выполнения O (N 2) (квадратично).

И наоборот, поскольку StringBuffer является изменяемым, ему не нужно копировать String каждый раз, когда вы выполняете Append (), поэтому последовательность из N вызовов Append () занимает O (N) времени (линейно). Это имеет существенное значение во время выполнения только в том случае, если вы добавляете большое количество строк вместе.

person Adam Rosenfield    schedule 15.09.2008

Как уже говорилось, объект String неизменяем, то есть после создания (см. Ниже) его нельзя изменить.

Строка x = новая Строка («что-то»); // или

Строка x = "что-то";

Поэтому, когда вы пытаетесь объединить объекты String, значения этих объектов берутся и помещаются в новый объект String.

Если вместо этого вы используете StringBuffer, который является изменяемым, вы постоянно добавляете значения во внутренний список char (примитивов), который может быть расширен или усечен, чтобы соответствовать необходимому значению. Никаких новых объектов не создается, только новые символы создаются / удаляются, когда это необходимо для хранения значений.

person Christian P.    schedule 15.09.2008

Когда вы объединяете две строки, вы фактически создаете третий объект String в Java. Использование StringBuffer (или StringBuilder в Java 5/6) быстрее, потому что он использует внутренний массив символов для хранения строки, и когда вы используете один из его методов add (...), он не создает новую строку объект. Вместо этого StringBuffer / Buider добавляет внутренний массив.

В простых конкатенациях не проблема, объединяете ли вы строки с помощью StringBuffer / Builder или оператора '+', но при выполнении большого количества конкатенаций строк вы увидите, что использование StringBuffer / Builder намного быстрее.

person Alexandre Brasil    schedule 15.09.2008

Причина в том, что String неизменяем. Вместо изменения строки он создает новую. Пул строк хранит все значения String до тех пор, пока сборщики мусора не обработают его. Подумайте о двух строках как Hello и how are you. Если мы рассмотрим пул String, он состоит из двух String.

введите описание изображения здесь

Если вы попытаетесь объединить эти две строки как,

строка1 = строка1 + строка2

Теперь создайте новый объект String и сохраните его в пуле String.

введите описание изображения здесь

Если мы попытаемся объединить тысячи слов, он получит больше памяти. Решением для этого является 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);
   }
}

вывод: закрытый по умолчанию защищенный общедоступный

person Priyantha    schedule 10.11.2019

Поскольку строки являются неизменяемыми в Java, каждый раз, когда вы объединяете строку, в памяти создается новый объект. SpringBuffer использует тот же объект в памяти.

person Ivan Bosnic    schedule 15.09.2008

Думаю, самый простой ответ: так быстрее.

Если вы действительно хотите узнать все, что скрывается под капотом, вы всегда можете сами взглянуть на источник:

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

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

person rgcb    schedule 15.09.2008

Раздел Оператор конкатенации строк + Спецификации языка Java дает вам дополнительную справочную информацию о том, почему оператор + может работать так медленно.

person Benedikt Waldvogel    schedule 15.09.2008