字符串串联:concat()vs" +"运算符

时间:2020-03-05 18:48:44  来源:igfitidea点击:

假设字符串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