Ответ 1
Помимо профилирования, у нас есть еще одна возможность получить некоторые идеи. Я хочу сосредоточиться на возможных различиях скорости, а не на том, что их снова удаляет.
Итак, давайте начнем с этого класса Test
:
public class Test {
// Do not optimize this
public static volatile String A = "A String";
public static void main( String [] args ) throws Exception {
String a1 = A + "B";
String a2 = A + 'B';
a1.equals( a2 );
}
}
Я скомпилировал это с помощью javac Test.java(используя javac -v: javac 1.7.0_55)
Используя javap -c Test.class, получаем:
Compiled from "Test.java"
public class Test {
public static volatile java.lang.String A;
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
7: getstatic #4 // Field A:Ljava/lang/String;
10: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
13: ldc #6 // String B
15: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
21: astore_1
22: new #2 // class java/lang/StringBuilder
25: dup
26: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
29: getstatic #4 // Field A:Ljava/lang/String;
32: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
35: bipush 66
37: invokevirtual #8 // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;
40: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
43: astore_2
44: aload_1
45: aload_2
46: invokevirtual #9 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
49: pop
50: return
static {};
Code:
0: ldc #10 // String A String
2: putstatic #4 // Field A:Ljava/lang/String;
5: return
}
Мы видим, что задействованы два StringBuilders (строки 4, 22). Итак, первое, что мы обнаруживаем, заключается в том, что использование +
для concat Strings
фактически совпадает с использованием StringBuilder.
Второе, что мы видим здесь, это то, что StringBuilders вызывается дважды. Сначала для добавления изменчивой переменной (строки 10, 32) и второй раз для добавления константной части (строки 15, 37)
В случае A + "B"
append
вызывается с аргументом Ljava/lang/String
(String), а в случае A + 'B'
он вызывается с аргументом C
(a char).
Таким образом, компилятор не преобразовывает String в char, но оставляет его как есть.
Теперь, находясь в AbstractStringBuilder
, который содержит используемые методы, мы имеем:
public AbstractStringBuilder append(char c) {
ensureCapacityInternal(count + 1);
value[count++] = c;
return this;
}
и
public AbstractStringBuilder append(String str) {
if (str == null) str = "null";
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
как фактически вызванные методы.
Самые дорогие операции здесь, конечно, ensureCapacity
, но только в том случае, если предел достигнут (он копирует массив старых StringBuffers char [] в новый). Так что это верно для обоих и не имеет реальной разницы.
Как видно, существует множество других операций, которые выполняются, но реальное различие между value[count++] = c;
и str.getChars(0, len, value, count);
Если мы посмотрим на getChars, мы увидим, что все это сводится к одному System.arrayCopy
, который используется здесь для копирования массива String в буфер, плюс некоторые проверки и дополнительные вызовы методов против одного доступа к массиву.
Поэтому я бы сказал, что теоретически использование A + "B"
намного медленнее, чем при использовании A + 'B'
.
Я думаю, что в реальном исполнении он медленнее. Но для определения этого нам нужно ориентироваться.
EDIT: Потому что это все до того, как JIT делает это волшебство. См. Ответ Стивена C для этого.
EDIT2: Я смотрел на байт-код, который генерировал компилятор eclipse, и он почти идентичен. Таким образом, по крайней мере, эти два компилятора не отличаются в результате.
EDIT2: И ТЕПЕРЬ ФУНКЦИЯ
Тесты. Этот результат генерируется путем запуска Loops 0..100M для a+'B'
и a+"B"
несколько раз после разминки:
a+"B": 5096 ms
a+'B': 4569 ms
a+'B': 4384 ms
a+"B": 5502 ms
a+"B": 5395 ms
a+'B': 4833 ms
a+'B': 4601 ms
a+"B": 5090 ms
a+"B": 4766 ms
a+'B': 4362 ms
a+'B': 4249 ms
a+"B": 5142 ms
a+"B": 5022 ms
a+'B': 4643 ms
a+'B': 5222 ms
a+"B": 5322 ms
в среднем:
a+'B': 4608ms
a+"B": 5167ms
Так что даже в реальном мире тестов синтаксических знаний (хе-хе) a+'B'
примерно на 10% быстрее, чем a+"B"
...
... по крайней мере (отказ от ответственности) в моей системе с моим компилятором и моим процессором, и он действительно не отличается/не заметен в реальных программах. Кроме причины, у вас есть кусок кода, который вы часто выполняете, и от этого зависит все ваши приложения. Но тогда вы, вероятно, сделаете что-то другое.
EDIT4:
Думая об этом. Это цикл, используемый для сравнения:
start = System.currentTimeMillis();
for( int i=0; i<RUNS; i++ ){
a1 = a + 'B';
}
end = System.currentTimeMillis();
System.out.println( "a+'B': " + (end-start) + " ms" );
поэтому мы действительно не только сравниваем одно, о чем мы заботимся, но, хотя производительность java loop, производительность создания объектов и назначение производительности переменных. Таким образом, реальная разница в скорости может быть даже немного больше.