Если объединить строки literal (буквально "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
. Это неэффективно (но это не имеет значения, если вы не делаете это lot ) из-за всех временных выделений памяти: оно выделяет StringBuilder
и его буфер, вполне возможно, перераспределяет буфер на первый append
[если rv
имеет длину более 16 символов, что является размером буфера по умолчанию], и если не в первом, то почти наверняка во втором append
, в конце выделяется String
в конце & mdash ; и затем делает это все снова на следующей итерации.
При необходимости вы можете повысить эффективность, переписав его, чтобы явно использовать 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-оптимизирующий компилятор. Как только он определил петлю как горячую точку, он вполне может найти способ ее рефакторинга. Или нет, конечно. : -)
Мое эмпирическое правило: я беспокоюсь об этом, когда вижу проблему с производительностью, или если я знаю, что выполняю много конкатенации, и очень вероятно, что 1056 * будет проблема с производительностью, и код не окажет значительного влияния с точки зрения обслуживания, если я вместо этого использую StringBuilder
. Бешеная лига против преждевременной оптимизации, вероятно, не согласилась бы со мной на втором из них. : -)