假设字符串a和b:
a += b
a = a.concat(b)
在引擎盖下,它们是一样的吗?
这里是concat反编译作为参考。我希望能够反编译+操作符以及看看它做什么。
public String concat(String s) {
int i = s.length();
if (i == 0) {
return this;
}
else {
char ac[] = new char[count + i];
getChars(0, count, ac, 0);
s.getChars(0, i, ac, count);
return new String(0, count + i, ac);
}
}
这里的大多数答案都来自2008年。随着时间的推移,情况似乎发生了变化。我最近用JMH做的基准测试显示,在Java 8 +上比concat快两倍左右。
我的基准:
@Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
public class StringConcatenation {
@org.openjdk.jmh.annotations.State(Scope.Thread)
public static class State2 {
public String a = "abc";
public String b = "xyz";
}
@org.openjdk.jmh.annotations.State(Scope.Thread)
public static class State3 {
public String a = "abc";
public String b = "xyz";
public String c = "123";
}
@org.openjdk.jmh.annotations.State(Scope.Thread)
public static class State4 {
public String a = "abc";
public String b = "xyz";
public String c = "123";
public String d = "!@#";
}
@Benchmark
public void plus_2(State2 state, Blackhole blackhole) {
blackhole.consume(state.a+state.b);
}
@Benchmark
public void plus_3(State3 state, Blackhole blackhole) {
blackhole.consume(state.a+state.b+state.c);
}
@Benchmark
public void plus_4(State4 state, Blackhole blackhole) {
blackhole.consume(state.a+state.b+state.c+state.d);
}
@Benchmark
public void stringbuilder_2(State2 state, Blackhole blackhole) {
blackhole.consume(new StringBuilder().append(state.a).append(state.b).toString());
}
@Benchmark
public void stringbuilder_3(State3 state, Blackhole blackhole) {
blackhole.consume(new StringBuilder().append(state.a).append(state.b).append(state.c).toString());
}
@Benchmark
public void stringbuilder_4(State4 state, Blackhole blackhole) {
blackhole.consume(new StringBuilder().append(state.a).append(state.b).append(state.c).append(state.d).toString());
}
@Benchmark
public void concat_2(State2 state, Blackhole blackhole) {
blackhole.consume(state.a.concat(state.b));
}
@Benchmark
public void concat_3(State3 state, Blackhole blackhole) {
blackhole.consume(state.a.concat(state.b.concat(state.c)));
}
@Benchmark
public void concat_4(State4 state, Blackhole blackhole) {
blackhole.consume(state.a.concat(state.b.concat(state.c.concat(state.d))));
}
}
结果:
Benchmark Mode Cnt Score Error Units
StringConcatenation.concat_2 thrpt 50 24908871.258 ± 1011269.986 ops/s
StringConcatenation.concat_3 thrpt 50 14228193.918 ± 466892.616 ops/s
StringConcatenation.concat_4 thrpt 50 9845069.776 ± 350532.591 ops/s
StringConcatenation.plus_2 thrpt 50 38999662.292 ± 8107397.316 ops/s
StringConcatenation.plus_3 thrpt 50 34985722.222 ± 5442660.250 ops/s
StringConcatenation.plus_4 thrpt 50 31910376.337 ± 2861001.162 ops/s
StringConcatenation.stringbuilder_2 thrpt 50 40472888.230 ± 9011210.632 ops/s
StringConcatenation.stringbuilder_3 thrpt 50 33902151.616 ± 5449026.680 ops/s
StringConcatenation.stringbuilder_4 thrpt 50 29220479.267 ± 3435315.681 ops/s
不,不完全是。
首先,语义上略有不同。如果a为空,则a.c concat(b)抛出NullPointerException,但a+=b将把a的原始值视为空值。此外,concat()方法只接受String值,而+运算符将无声地将参数转换为String(对对象使用toString()方法)。因此concat()方法在接受内容方面更加严格。
为了深入了解,用a += b写一个简单的类;
public class Concat {
String cat(String a, String b) {
a += b;
return a;
}
}
现在使用javap -c(包含在Sun JDK中)进行反汇编。您应该看到一个清单,包括:
java.lang.String cat(java.lang.String, java.lang.String);
Code:
0: new #2; //class java/lang/StringBuilder
3: dup
4: invokespecial #3; //Method java/lang/StringBuilder."<init>":()V
7: aload_1
8: invokevirtual #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
11: aload_2
12: invokevirtual #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: invokevirtual #5; //Method java/lang/StringBuilder.toString:()Ljava/lang/ String;
18: astore_1
19: aload_1
20: areturn
a += b等于
a = new StringBuilder()
.append(a)
.append(b)
.toString();
concat方法应该更快。但是,对于更多的字符串,StringBuilder方法胜出,至少在性能方面是这样。
String和StringBuilder的源代码(以及它的包私有基类)可以在Sun JDK的src.zip中找到。您可以看到您正在构建一个char数组(根据需要调整大小),然后在创建最终String时将其丢弃。实际上,内存分配的速度快得惊人。
更新:正如Pawel Adamski所指出的,性能在最近的HotSpot中有所改变。Javac仍然生成完全相同的代码,但是字节码编译器会作弊。简单的测试完全失败,因为整个代码体都被丢弃了。总结系统。identityHashCode(不是String.hashCode)显示StringBuffer代码有一点优势。可能会在发布下一次更新时更改,或者如果您使用不同的JVM。来自@lukaseder的HotSpot JVM intrinsic列表。
我运行了类似于@marcio的测试,但使用了以下循环:
String c = a;
for (long i = 0; i < 100000L; i++) {
c = c.concat(b); // make sure javac cannot skip the loop
// using c += b for the alternative
}
为了更好的度量,我还添加了StringBuilder.append()。每个测试运行10次,每次运行100,000次。以下是调查结果:
StringBuilder轻松获胜。大多数运行的时钟时间结果为0,最长的运行时间为16毫秒。
A += b每次运行大约需要40000ms (40s)。
Concat每次运行只需要10000ms (10s)。
我还没有反编译类来查看内部结构或通过分析器运行它,但我怀疑a += b花了很多时间来创建StringBuilder的新对象,然后将它们转换回String。
做一些简单的测试怎么样?使用下面的代码:
long start = System.currentTimeMillis();
String a = "a";
String b = "b";
for (int i = 0; i < 10000000; i++) { //ten million times
String c = a.concat(b);
}
long end = System.currentTimeMillis();
System.out.println(end - start);
“a + b”版本在2500ms内执行。
a.concat(b)在1200ms内执行。
测试了几次。concat()版本的执行平均花费了一半的时间。
这个结果让我很惊讶,因为concat()方法总是创建一个新字符串(它返回一个“new string (result)”)。众所周知:
String a = new String("a") // more than 20 times slower than String a = "a"
为什么编译器不能在“a + b”代码中优化字符串创建,知道它总是导致相同的字符串?它可以避免创建新的字符串。
如果你不相信上面的说法,自己测试一下。