字符串串联:concat()vs" +"运算符
假设字符串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); } }
解决方案
回答
+运算符可以在字符串和字符串,char,integer,double或者float数据类型值之间工作。它只是在连接前将值转换为其字符串表示形式。
concat运算符只能在字符串上使用,也不能在字符串上使用。如果它们不匹配,它将检查数据类型的兼容性并引发错误。
除此之外,我们提供的代码具有相同的功能。
回答
我不这么认为。
a.concat(b)
是在String中实现的,我认为自早期的Java机器以来,实现并没有太大变化。 " +"操作的实现取决于Java版本和编译器。当前,使用StringBuffer实现+,以使操作尽可能快。也许将来会有所改变。在Java的早期版本中,对String的+操作会慢得多,因为它会产生中间结果。
我猜想+ =
是使用+
实现的,并且经过类似的优化。
回答
Niyaz是正确的,但是也值得注意的是,特殊的+运算符可以被Java编译器转换成更有效的形式。 Java具有StringBuilder类,该类表示非线程安全的可变String。当执行一串String串联时,Java编译器会静默转换
String a = b + c + d;
进入
String a = new StringBuilder(b).append(c).append(d).toString();
对于大型字符串,它的效率明显更高。据我所知,使用concat方法时不会发生这种情况。
但是,将空字符串连接到现有字符串时,concat方法效率更高。在这种情况下,JVM不需要创建新的String对象,而只需返回现有的对象即可。请参阅concat文档以确认这一点。
因此,如果我们非常关注效率,那么在连接可能为空的字符串时应使用concat方法,否则应使用+。但是,性能差异可以忽略不计,我们可能永远不必担心这一点。
回答
不,不是。
首先,语义上略有不同。如果a为null,则a.concat(b)抛出NullPointerException,但是a + = b将a的原始值视为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方法应该更快。但是,使用更多的字符串,至少在性能上,StringBuilder
方法会获胜。
Sun JDK的src.zip中提供了String和StringBuilder的源代码(及其封装专用的基类)。我们会看到正在建立一个char数组(根据需要调整大小),然后在创建最终的String
时将其丢弃。实际上,内存分配出奇的快。
更新:正如Pawel Adamski指出的那样,在最近的HotSpot中,性能已经发生了变化。 javac
仍然会产生完全相同的代码,但是字节码编译器会作弊。简单的测试完全失败,因为整个代码主体都被丢弃了。总结" System.identityHashCode"(而不是" String.hashCode")显示" StringBuffer"代码有一点优势。在发布下一个更新时,或者使用其他JVM时,可能会发生更改。在@lukaseder中,列出了HotSpot JVM内在函数。
回答
一些简单的测试怎么样?使用下面的代码:
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"版本在2500毫秒内执行。
- a.concat(b)在1200毫秒内执行。
经过多次测试。 concat()版本的执行平均花费了一半的时间。
这个结果让我感到惊讶,因为concat()
方法总是创建一个新字符串(它返回一个"new String(result)
"。众所周知:
String a = new String("a") // more than 20 times slower than String a = "a"
为什么编译器不知道" a + b"代码总是导致相同的字符串,所以不能优化" a + b"代码中的字符串创建?它可以避免创建新的字符串。
如果我们不相信上述说法,请进行自我测试。
回答
Tom正确地描述了+运算符的作用是正确的。它创建一个临时的StringBuilder
,添加零件,并以toString()
结尾。
但是,到目前为止,所有答案都忽略了HotSpot运行时优化的影响。具体来说,这些临时操作被认为是一种通用模式,并在运行时被更有效的机器代码所取代。
@marcio:我们已经创建了一个微基准测试;对于现代的JVM,这不是分析代码的有效方法。
运行时优化很重要的原因是,一旦HotSpot开始运行,代码中的许多差异(甚至包括对象创建)都将完全不同。唯一可以确定的方法是就地分析代码。
最后,所有这些方法实际上都非常快。这可能是过早优化的情况。如果我们有很多将字符串连接在一起的代码,则获得最大速度的方法可能与我们选择的运算符,而不是所使用的算法无关!
回答
我运行了与@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次,每次运行10万次。结果如下:
StringBuilder
赢得了胜利。大多数运行的时钟时间结果均为0,最长的时间为16ms。- 每次运行
a + = b
大约需要40000ms(40s)。 concat
每次运行只需要10000ms(10s)。
我还没有反编译该类以查看内部结构或者通过事件探查器运行它,但是我怀疑a + = b
花了很多时间来创建StringBuilder
的新对象,然后将它们转换回String
。