Я читал о способе, с которым работает Java +=
оператор, с помощью StringBuilder
.
Действительно ли это - то же с a ("a" + "b")
операция?
Нет. Использование
StringBuilder
не то же самое, что выполнение«a» + «b»
.
В Java экземпляры String неизменяемы.
Итак, если вы это сделаете:
String c = "a" + "b";
Вы создаете новые строки каждый раз при объединении.
С другой стороны, StringBuilder похож на буфер, который может увеличиваться по мере необходимости при добавлении новых строк.
StringBuilder c = new StringBuilder();
c.append("a");
c.append("b"); // c is only created once and appended "a" and "b".
Практическое правило (изменено благодаря комментариям, которые я получил):
Если вы собираетесь объединить много (например, объединить внутри цикла или сгенерировать большой XML, образованный несколькими конкатенированными переменными строк), действительно используйте StringBuilder. В противном случае простая конкатенация (с использованием оператора +) подойдет.
Оптимизация компилятора также играет огромную роль при компиляции такого кода.
Вот дальнейшее объяснение по теме.
И другие вопросы StackOVerflow по этой проблеме:
Лучше ли повторно использовать StringBuilder в цикле?
Как лучше всего построить строку элементов с разделителями в Java?
Если вы объедините буквальные строки (буквально «foo» + «bar»
), компилятор сделает это во время компиляции, а не во время выполнения.
Если у вас есть две нелитеральные строки и соедините их с помощью +
, компилятор (во всяком случае, Sun) будет использовать StringBuilder
под оболочкой, но не обязательно в большинстве эффективный способ. Так, например, если у вас есть это:
String repeat(String a, int count) {
String rv;
if (count <= 0) {
return "";
}
rv = a;
while (--count > 0) {
rv += a;
}
return rv;
}
... то, что компилятор Sun на самом деле создаст в виде байт-кода, выглядит примерно так :
String repeat(String a, int count) {
String rv;
if (count <= 0) {
return "";
}
rv = a;
while (--count > 0) {
rv = new StringBuilder().append(rv).append(a).toString();
}
return rv;
}
(Да, действительно - см. Разборку в конце этот ответ.) Обратите внимание, что он создавал новый StringBuilder
на каждой итерации, а затем преобразовывал результат в String
. Это неэффективно (но это не имеет значения, если вы не делаете это лот ) из-за всех выделений временной памяти: он выделяет StringBuilder
и его буфер, вполне возможно перераспределяет буфер в первом добавлении
[если rv
имеет длину более 16 символов, что является размером буфера по умолчанию] и если не в первом, то почти наверняка во втором ] append
, затем выделяет String
в конце - а затем делает это снова на следующей итерации.
Вы можете повысить эффективность, если необходимо, переписав его так, чтобы он явно использовал StringBuilder
:
String repeat(String a, int count) {
StringBuilder rv;
if (count <= 0) {
return "";
}
rv = new StringBuilder(a.length() * count);
while (count-- > 0) {
rv.append(a);
}
return rv.toString();
}
Здесь мы использовали явный StringBuilder
, а также установили его начальную емкость буфера. быть достаточно большим, чтобы удерживать результат. Это более эффективно с точки зрения памяти, но, конечно, немного менее понятно для неопытных разработчиков кода и немного сложнее для написания. Так что , если вы обнаружите проблему с производительностью из-за жесткого цикла конкатенации строк, это может быть одним из способов ее решения.
Вы можете увидеть этот скрытый StringBuilder
в действии с помощью следующего тестового класса:
public class SBTest
{
public static final void main(String[] params)
{
System.out.println(new SBTest().repeat("testing ", 4));
System.exit(0);
}
String repeat(String a, int count) {
String rv;
if (count <= 0) {
return "";
}
rv = a;
while (--count > 0) {
rv += a;
}
return rv;
}
}
... который дизассемблирует (с помощью javap -c SBTest
) следующим образом :
Compiled from "SBTest.java"
public class SBTest extends java.lang.Object{
public SBTest();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static final void main(java.lang.String[]);
Code:
0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3: new #3; //class SBTest
6: dup
7: invokespecial #4; //Method "<init>":()V
10: ldc #5; //String testing
12: iconst_4
13: invokevirtual #6; //Method repeat:(Ljava/lang/String;I)Ljava/lang/String;
16: invokevirtual #7; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
19: iconst_0
20: invokestatic #8; //Method java/lang/System.exit:(I)V
23: return
java.lang.String repeat(java.lang.String, int);
Code:
0: iload_2
1: ifgt 7
4: ldc #9; //String
6: areturn
7: aload_1
8: astore_3
9: iinc 2, -1
12: iload_2
13: ifle 38
16: new #10; //class java/lang/StringBuilder
19: dup
20: invokespecial #11; //Method java/lang/StringBuilder."<init>":()V
23: aload_3
24: invokevirtual #12; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
27: aload_1
28: invokevirtual #12; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31: invokevirtual #13; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34: astore_3
35: goto 9
38: aload_3
39: areturn
}
Обратите внимание, как новый StringBuilder
создается на каждой итерации цикла и создается с использованием емкости буфера по умолчанию.
Все это временное выделение памяти звучит некрасиво, но опять же, только если вы имеете дело с существенными циклами и / или значительными строками. Кроме того, когда полученный байт-код запускается, JVM вполне может оптимизировать его дальше. Например, Sun HotSpot JVM - это очень зрелый оптимизирующий компилятор JIT. Как только он определил цикл как горячую точку, он вполне может найти способ его реорганизовать. Или нет, конечно. : -)
Мое практическое правило: я беспокоюсь об этом, когда вижу проблему с производительностью или знаю, что выполняю много конкатенации, и это очень вероятно будет проблемой с производительностью, и если я использую вместо этого StringBuilder
, код не будет существенно затронут с точки зрения ремонтопригодности.Лига яростных противников преждевременной оптимизации, вероятно, не согласится со мной по второму из них. : -)
Строки чаще объединяются с оператором +, как в
"Привет," + "мир" + "!"
Да, это то же самое, но компилятор может дополнительно оптимизировать конкатенацию литералов перед выдачей кода, поэтому "a "+" b "
может быть выдано напрямую как " ab "
.
Для конкатенации фиксированного количества строк в одном выражении с +
компилятор создаст код, используя одиночный StringBuilder
.
Например. строка
String d = a + b + c;
дает тот же байт-код, что и строка
String d = new StringBuilder().append(a).append(b).append(c).toString();
при компиляции с использованием компилятора javac. (Компилятор Eclipse создает несколько более оптимизированный код, вызывая new StringBuilder (a)
, тем самым сохраняя один вызов метода.)
Как упоминалось в других ответах, компилятор объединит строковые литералы, такие как " a "+" b "
в одну строку, создавая вместо этого байт-код, содержащий " ab "
.
Как упоминалось повсюду в сети, вы не должны использовать +
для создания одной строки внутри цикла , потому что вы снова и снова копируете начало строки в новый струны. В этой ситуации вы должны использовать один StringBuilder
, который вы объявляете вне цикла.
«a» + «b»
операция
Хотя конкатенация строк с помощью «+» читабельна, легко форматируется и прямолинейна, она считается плохой в Java.
Каждый раз, когда вы добавляете что-либо через '+' (String.concat ()), создается новая строка, копируется старое содержимое строки, добавляется новое содержимое, а старая строка удаляется. Чем больше Строка, тем больше времени требуется - нужно копировать больше, и получается больше мусора. Примечание: если вы просто объединяете несколько (скажем, 3,4) строк, а не построив строку с помощью цикла или просто написав какое-нибудь тестовое приложение, вы все равно можете придерживаться «+»
Используя
StringBuilder
При выполнении обширных манипуляций со строкой (или добавлении через цикл), заменив «+» на StringBuilder
.append, вероятно, рекомендуется. Промежуточные объекты, упомянутые в случае «+», не создаются во время вызова метода append ()
.
Также следует отметить оптимизацию в компиляторе Sun Java, который автоматически создает StringBuilders
( StringBuffers
<5.0), когда видит конкатенации строк. Но это всего лишь компилятор Sun Java.