string连接:concat()vs“+”运算符
假设stringa和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); } }
不,不完全。
首先,语义有一些细微的差别。 如果a
为null
,则a.concat(b)
抛出一个NullPointerException
exception,但是a+=b
会将a+=b
的原始值视为null
。 此外, 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
方法应该更快。 但是,对于更多的string, StringBuilder
方法至less会在性能方面获胜。
String
和StringBuilder
的源代码(及其包私有的基类)在Sun JDK的src.zip中可用。 你可以看到你正在build立一个char数组(根据需要resize),然后在创build最终的String
时将其丢弃。 在实践中,内存分配出奇的快。
更新:正如Pawel Adamski所指出的,在最近的HotSpot中,性能已经发生了变化。 javac
仍然产生完全相同的代码,但字节码编译器作弊。 简单的testing完全失败,因为整个代码被扔掉了。 总结System.identityHashCode
(不是String.hashCode
)显示StringBuffer
代码有一个小小的优势。 在下一次更新发布时或者如果您使用不同的JVM,可能会有所变化。 来自@lukaseder , 一个HotSpot JVM内部函数列表 。
Niyaz是正确的,但也值得注意的是,特殊的+运算符可以被Java编译器转换成更高效的东西。 Java有一个StringBuilder类,它表示一个非线程安全的可变String。 当执行一串string连接时,Java编译器默默地转换
String a = b + c + d;
成
String a = new StringBuilder(b).append(c).append(d).toString();
这对于大string来说效率更高。 据我所知,当你使用concat方法时不会发生这种情况。
但是,将空string连接到现有string时,concat方法更有效。 在这种情况下,JVM不需要创build一个新的String对象,只需返回现有的对象。 请参阅concat文档以确认这一点。
所以如果你对效率超级关心,那么你应该在连接可能为空的string时使用concat方法,否则使用+。 然而,性能差异应该可以忽略不计,你可能不应该担心这一点。
我运行了一个类似于@marcio的testing,但用下面的循环代替:
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()
。 每次testing运行10次,每次运行10万次。 结果如下:
-
StringBuilder
胜手。 时钟时间结果大多数是0,最长的时间是16ms。 -
a += b
每次运行约需要40000ms(40s)。 -
concat
每次运行只需要10000ms(10s)。
我没有反编译类看内部或通过分析器运行它,但我怀疑a += b
花费大量的时间创buildStringBuilder
新对象,然后将它们转换回String
。
Tom在描述+运算符的确切含义是正确的。 它创build一个临时的StringBuilder
,附加部分,并完成toString()
。
然而,到目前为止所有的答案都忽略了HotSpot运行时优化的效果。 具体而言,这些临时操作被认为是一种常见的模式,并在运行时被更高效的机器代码所取代。
@marcio:你已经创build了一个微型基准 ; 与现代JVM的这是不是一个有效的方法来剖析代码。
运行时优化的原因很重要,即使包括对象创build在内的代码中的许多差异,一旦HotSpot进入,它们将完全不同。 唯一可以肯定的方法就是在原处分析代码。
最后,所有这些方法实际上都非常快。 这可能是一个过早优化的情况。 如果你有连接string的代码很多,获得最大速度的方式可能与你select的操作符无关,而是你正在使用的algorithm!
如何简单的testing? 使用下面的代码:
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内执行。
testing了几次。 concat()
版本的执行平均花了一半的时间。
这个结果令我感到惊讶,因为concat()
方法总是创build一个新的string(它返回一个“ new String(result)
”。众所周知:
String a = new String("a") // more than 20 times slower than String a = "a"
为什么编译器不能在“a + b”代码中优化string创build,知道它总是导致相同的string? 它可以避免一个新的string创build。 如果你不相信上面的陈述,那么testing你的自我。
我不这么认为。 a.concat(b)在String中实现,我认为从早期的java机器实现并没有太大的改变。 +操作实现取决于Java版本和编译器。 目前+使用StringBuffer实现尽可能快的操作。 也许将来这会改变。 在早期的版本中,对Strings的java +操作要比生成中间结果慢得多。 我想+ =是用+和类似的优化实现的。
这里的大多数答案都是从2008年开始的。看起来事情已经发生了变化。 我使用JMH进行的最新testing表明,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
基本上,+和concat
方法有两个重要的区别。
-
如果你正在使用concat方法,那么你只能连接string,而在+运算符的情况下,你也可以连接任何数据types的string。
例如:
String s = 10 + "Hello";
在这种情况下,输出应该是10Hello 。
String s = "I"; String s1 = s.concat("am").concat("good").concat("boy"); System.out.println(s1);
在上述情况下,您必须提供两个string。
-
+和concat之间的第二个主要区别是:
案例1:假设我用concat操作符以这种方式连接相同的string
String s="I"; String s1=s.concat("am").concat("good").concat("boy"); System.out.println(s1);
在这种情况下,池中创build的对象的总数是7个,如下所示:
I am good boy Iam Iamgood Iamgoodboy
案例2:
现在我要通过+运算符来协调相同的string
String s="I"+"am"+"good"+"boy"; System.out.println(s);
在上述情况下,创build的对象总数只有5个。
实际上,当我们通过+运算符来分析string时,它会维护一个StringBuffer类来执行相同的任务,如下所示:
StringBuffer sb = new StringBuffer("I"); sb.append("am"); sb.append("good"); sb.append("boy"); System.out.println(sb);
这样它将只创build五个对象。
所以,这些是+和concat方法之间的基本区别。 请享用 :)
+运算符可以在string和string,char,integer,double或float数据types值之间工作。 它只是在连接之前将值转换为其string表示forms。
concat操作符只能在string上进行。 它检查数据types兼容性,如果它们不匹配则抛出一个错误。
除此之外,你提供的代码也是一样的。
为了完整起见,我想补充一点,“+”运算符的定义可以在JLS SE8 15.18.1中find :
如果只有一个操作数expression式是Stringtypes,则在另一个操作数上执行string转换(第5.1.11节)以在运行时产生string。
string连接的结果是对两个操作数string串联的String对象的引用。 左侧操作数的字符位于新创build的string中右侧操作数的字符之前。
除非expression式是常量expression式(§15.28),否则String对象是新创build的(§12.5)。
关于实施JLS说:
实现可以select一步执行转换和连接,以避免创build并丢弃中间的String对象。 为了提高重复string连接的性能,Java编译器可以使用StringBuffer类或类似技术来减less通过评估expression式创build的中间String对象的数量。
对于基本types,实现也可以通过直接从基元types转换为string来优化封装器对象的创build。
因此,从“Java编译器可能使用StringBuffer类或类似的技术来减less”来判断,不同的编译器可能产生不同的字节码。
当使用+时,速度随着string长度的增加而减小,但是当使用concat时,速度更加稳定,最好的select是使用速度稳定的StringBuilder类。
我想你可以理解为什么。 但是创build长string的最好方法是使用StringBuilder()和append(),否则速度将是不可接受的。