Кто-то сказал мне, что для объединения строк в Java более эффективно использовать StringBuffer
, чем использовать оператор +
для String
s. Что происходит под капотом, когда вы это делаете? Что StringBuffer
делает по-другому?
Зачем использовать StringBuffer в Java вместо оператора конкатенации строк
Ответы (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
- String
s является неизменным). Это очень расточительно. Замена этого на один 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);
}
Использование строки в этом цикле сгенерирует в памяти 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
создавались бы в процессе построения конечной строки.
Однако как 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()
.
Под капотом он фактически создает и добавляет к StringBuffer, вызывая toString () для результата. Так что на самом деле не имеет значения, что вы используете.
So
String s = "a" + "b" + "c";
становится
String s = new StringBuffer().append("a").append("b").append("c").toString();
Это верно для группы встроенных добавлений в одном операторе. Если вы создаете свою строку в ходе нескольких операторов, вы тратите память, и StringBuffer или StringBuilder - ваш лучший выбор.
Я думаю, что с учетом jdk1.5 (или выше) и вашей конкатенации поточно-ориентированной вы должны использовать StringBuilder вместо StringBuffer http://java4ever.blogspot.com/2007/03/string-vs-stringbuffer-vs-stringbuilder.html Что касается увеличения скорости: http://www.about280.com/stringtest.html
Лично я бы кодировал для удобочитаемости, поэтому, если вы не обнаружите, что конкатенация строк значительно замедляет ваш код, оставайтесь с тем методом, который делает ваш код более читаемым.
В некоторых случаях это устарело из-за оптимизации, выполненной компилятором, но общая проблема заключается в том, что код вроде:
string myString="";
for(int i=0;i<x;i++)
{
myString += "x";
}
будет действовать, как показано ниже (каждый шаг является следующей итерацией цикла):
- создать строковый объект длиной 1 и значением «x»
- Создайте новый строковый объект размером 2, скопируйте в него старую строку «x», добавьте «x» в позицию 2.
- Создайте новый строковый объект размером 3, скопируйте в него старую строку «xx», добавьте «x» в позицию 3.
- ... и так далее
Как видите, на каждой итерации необходимо копировать еще один символ, в результате чего мы выполняем 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 приведет к двум конструкциям StringBuffer с двумя вызовами toString (). Если бы у вас была длинная цепочка конкатов, это превратилось бы в настоящий беспорядок. Делая это самостоятельно, вы могли контролировать это и делать это должным образом.
Java 5.0 и выше, кажется, делают это более разумно, поэтому это меньше проблем и, конечно, менее многословно.
AFAIK это зависит от версии JVM, в версиях до 1.5 использование «+» или «+ =» фактически каждый раз копировало всю строку.
Помните, что использование + = фактически выделяет новую копию строки.
Как было указано, использование + in циклов подразумевает копирование.
Когда строки, которые объединяются, являются константами времени компиляции, они объединяются во время компиляции, поэтому
String foo = "a" + "b" + "c";
Собран для:
String foo = "abc";
Класс StringBuffer поддерживает массив символов для хранения содержимого строк, которые вы объединяете, тогда как метод + создает новую строку при каждом вызове и добавляет два параметра (param1 + param2).
StringBuffer работает быстрее, потому что 1. он может использовать свой уже существующий массив для объединения / хранения всех строк. 2. даже если они не помещаются в массив, быстрее выделить больший резервный массив, чем создавать новые объекты String для каждого вызова.
Дальнейшая информация:
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 ..
}
StringBuffer изменяемый. Он добавляет значение строки к тому же объекту без создания экземпляра другого объекта. Сделать что-то вроде:
myString = myString + "XYZ"
создаст новый объект String.
Чтобы объединить две строки с помощью '+', необходимо выделить новую строку с пространством для обеих строк, а затем данные скопировать из обеих строк. StringBuffer оптимизирован для объединения и выделяет больше места, чем необходимо изначально. Когда вы объединяете новую строку, в большинстве случаев символы можно просто скопировать в конец существующего строкового буфера.
Для объединения двух строк оператор '+', вероятно, будет иметь меньше накладных расходов, но по мере объединения больше strings, StringBuffer выйдет вперед, используя меньше выделений памяти и меньше копий данных.
Поскольку строки неизменяемы, каждый вызов оператора + создает новый объект String и копирует данные String в новую строку. Поскольку копирование String занимает время, линейное по длине String, последовательность из N вызовов оператора + приводит к времени выполнения O (N 2) (квадратично).
И наоборот, поскольку StringBuffer является изменяемым, ему не нужно копировать String каждый раз, когда вы выполняете Append (), поэтому последовательность из N вызовов Append () занимает O (N) времени (линейно). Это имеет существенное значение во время выполнения только в том случае, если вы добавляете большое количество строк вместе.
Как уже говорилось, объект String неизменяем, то есть после создания (см. Ниже) его нельзя изменить.
Строка x = новая Строка («что-то»); // или
Строка x = "что-то";
Поэтому, когда вы пытаетесь объединить объекты String, значения этих объектов берутся и помещаются в новый объект String.
Если вместо этого вы используете StringBuffer, который является изменяемым, вы постоянно добавляете значения во внутренний список char (примитивов), который может быть расширен или усечен, чтобы соответствовать необходимому значению. Никаких новых объектов не создается, только новые символы создаются / удаляются, когда это необходимо для хранения значений.
Когда вы объединяете две строки, вы фактически создаете третий объект String в Java. Использование StringBuffer (или StringBuilder в Java 5/6) быстрее, потому что он использует внутренний массив символов для хранения строки, и когда вы используете один из его методов add (...), он не создает новую строку объект. Вместо этого StringBuffer / Buider добавляет внутренний массив.
В простых конкатенациях не проблема, объединяете ли вы строки с помощью StringBuffer / Builder или оператора '+', но при выполнении большого количества конкатенаций строк вы увидите, что использование StringBuffer / Builder намного быстрее.
Причина в том, что 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);
}
}
вывод: закрытый по умолчанию защищенный общедоступный
Поскольку строки являются неизменяемыми в Java, каждый раз, когда вы объединяете строку, в памяти создается новый объект. SpringBuffer использует тот же объект в памяти.
Думаю, самый простой ответ: так быстрее.
Если вы действительно хотите узнать все, что скрывается под капотом, вы всегда можете сами взглянуть на источник:
http://www.sun.com/software/opensource/java/getinvolved.jsp
http://download.java.net/jdk6/latest/archive/
Раздел Оператор конкатенации строк + Спецификации языка Java дает вам дополнительную справочную информацию о том, почему оператор + может работать так медленно.