Скажем, у нас есть код, например:
public static void main(String[] args) {
String s = "";
for(int i=0 ; i<10000 ; i++) {
s += "really ";
}
s += "long string.";
}
(Да, я знаю, что гораздо лучшая реализация будет использовать StringBuilder
, но нести меня.)
Тривиально, мы могли бы ожидать, что байт-код, произведенный, будет чем-то вроде следующего:
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String
2: astore_1
3: iconst_0
4: istore_2
5: iload_2
6: sipush 10000
9: if_icmpge 25
12: aload_1
13: ldc #3 // String really
15: invokevirtual #4 // Method java/lang/String.concat:(Ljava/lang/String;)Ljava/lang/String;
18: astore_1
19: iinc 2, 1
22: goto 5
25: aload_1
26: ldc #5 // String long string.
28: invokevirtual #4 // Method java/lang/String.concat:(Ljava/lang/String;)Ljava/lang/String;
31: astore_1
32: return
Однако вместо этого компилятор пытается быть более умным - вместо использования метода concat у него есть испеченная оптимизация для использования объектов StringBuilder
, поэтому мы получаем следующее:
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String
2: astore_1
3: iconst_0
4: istore_2
5: iload_2
6: sipush 10000
9: if_icmpge 38
12: new #3 // class java/lang/StringBuilder
15: dup
16: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
19: aload_1
20: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: ldc #6 // String really
25: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore_1
32: iinc 2, 1
35: goto 5
38: new #3 // class java/lang/StringBuilder
41: dup
42: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
45: aload_1
46: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
49: ldc #8 // String long string.
51: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
54: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
57: astore_1
58: return
Однако для меня это кажется скорее контрпродуктивным - вместо использования одного строкового построителя для всего цикла каждый создается для каждой отдельной операции конкатенации, что делает его эквивалентным следующему:
public static void main(String[] args) {
String s = "";
for(int i=0 ; i<10000 ; i++) {
s = new StringBuilder().append(s).append("really ").toString();
}
s = new StringBuilder().append(s).append("long string.").toString();
}
Итак, вместо первоначального тривиального плохого подхода просто создав множество строковых объектов и отбросив их, компилятор создал гораздо худший подход к созданию множества объектов String
, много объектов StringBuilder
, вызывая больше методов и все еще бросая их всех, чтобы генерировать тот же результат, что и без этой оптимизации.
Итак, вопрос должен быть - почему? Я понимаю, что в таких случаях:
String s = getString1() + getString2() + getString3();
... компилятор создаст только один объект StringBuilder
для всех трех строк, поэтому бывают случаи, когда оптимизация полезна. Тем не менее, рассмотрение байт-кода показывает, что даже разделение приведенного выше случая на следующее:
String s = getString1();
s += getString2();
s += getString3();
... означает, что мы вернулись к случаю, когда три объекта StringBuilder
создаются индивидуально. Я бы понял, если это были нечетные угловые случаи, но добавление к ним строк (и в цикле) действительно довольно распространенные операции.
Конечно, было бы тривиально определять во время компиляции, если генерируемое компилятором StringBuilder
только когда-либо добавленное одно значение - и, если это было так, вместо этого используйте простое выполнение concat?
Это все с 8u5 (однако, оно вернется, по крайней мере, к Java 5, вероятно, раньше.) FWIW, мои тесты (неудивительно) поставили ручной concat()
подход в 2 раза быстрее, чем использование +=
в цикле с 10 000 элементов. Конечно, использование руководства StringBuilder
всегда является предпочтительным подходом, но, безусловно, компилятор не должен отрицательно влиять на производительность подхода +=
:?