除了分析这一点之外,我们还有另一种可能性来获得一些见解。我想把重点放在可能的速度差异上,而不是再次删除它们的东西上。
因此,让我们从这个类开始: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
}
我们可以看到,涉及两个StringBuilder(第4,22行)。因此,我们发现的第一件事是,使用concat实际上与使用StringBuilder相同。+
Strings
我们在这里可以看到的第二件事是StringBuilders都被调用了两次。第一个用于附加易失性变量(第 10、32 行),第二个用于附加常量部分(第 15、37 行)
如果 是 用 (字符串) 参数调用 的,而在 它的情况下用 (char) 参数调用。A + "B"
append
Ljava/lang/String
A + 'B'
C
因此,编译不会将 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;
}
作为实际调用的方法。
这里最昂贵的操作当然是,但只有在达到限制的情况下(它会将旧的StringBuffers char[]的数组复制到新的数组中)。因此,这对两者都是正确的,并没有真正的区别。ensureCapacity
正如人们所看到的,还有许多其他操作已经完成,但真正的区别在于value[count++] = c;
str.getChars(0, len, value, count);
如果我们查看getChars,我们会看到,它归结为一个在这里用于将String复制到缓冲区数组的数组,加上一些检查和其他方法调用,而不是单个数组访问。System.arrayCopy
所以我想说,从理论上讲,使用比使用慢得多。A + "B"
A + 'B'
我认为在实际执行中,它也更慢。但要确定这一点,我们需要进行基准测试。
编辑:因为这只是在JIT做魔术之前的全部。看看斯蒂芬·C对此的回答。
编辑2:我一直在看eclipse的编译器生成的字节码,它几乎是相同的。因此,至少这两个编译器在结果上没有差异。
编辑2:现在是有趣的部分
基准测试。此结果是通过在预热后运行循环 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'
a+"B"
...至少(免责声明)在我的系统上使用我的编译器和CPU,这在现实世界的程序中真的没有区别/不明显。除了原因之外,你有一段代码,你经常运行,你的所有应用程序性能都取决于此。但是,你可能会首先做不同的事情。
编辑4:
想想看。这是用于基准测试的循环:
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循环性能,对象创建性能和分配给变量的性能。因此,实际速度差异可能更大一些。