当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《字符串连接:concat()vs"+"运算符》经验,为你挑选了7个好方法。

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

Tom Hawtin -.. 543

不,不完全.

首先,语义略有不同.如果anull,则a.concat(b)抛出a NullPointerExceptiona+=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."":()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方法至少在性能方面获胜.

StringStringBuilderJDK的src.zip中提供了(和它的包 - 私有基类)的源代码.您可以看到正在构建一个char数组(根据需要调整大小),然后在创建final时将其抛弃String.实际上,内存分配速度惊人.

更新:正如Pawel Adamski所说,在最近的HotSpot中,性能发生了变化.javac仍然生成完全相同的代码,但字节码编译器作弊.简单的测试完全失败,因为整个代码都被抛弃了.求和System.identityHashCode(不String.hashCode)表明StringBuffer代码有一点点优势.在下次更新发布时,或者您使用其他JVM时,可能会发生更改.来自@lukaseder,HotSpot JVM内在函数列表.



1> Tom Hawtin -..:

不,不完全.

首先,语义略有不同.如果anull,则a.concat(b)抛出a NullPointerExceptiona+=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."":()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方法至少在性能方面获胜.

StringStringBuilderJDK的src.zip中提供了(和它的包 - 私有基类)的源代码.您可以看到正在构建一个char数组(根据需要调整大小),然后在创建final时将其抛弃String.实际上,内存分配速度惊人.

更新:正如Pawel Adamski所说,在最近的HotSpot中,性能发生了变化.javac仍然生成完全相同的代码,但字节码编译器作弊.简单的测试完全失败,因为整个代码都被抛弃了.求和System.identityHashCode(不String.hashCode)表明StringBuffer代码有一点点优势.在下次更新发布时,或者您使用其他JVM时,可能会发生更改.来自@lukaseder,HotSpot JVM内在函数列表.


@HyperLink您可以在使用它的已编译类上使用`javap -c`查看代码.(哦,就像在答案中一样.你只需要解释字节码反汇编,这应该不那么困难.)
自从创建此答案以来,情况发生了变化。请阅读下面的答案。

2> Eli Courtwri..:

Niyaz是正确的,但值得注意的是,特殊的+运算符可以被Java编译器转换为更高效的东西.Java有一个StringBuilder类,它表示一个非线程安全的可变String.执行一堆字符串连接时,Java编译器会静默转换

String a = b + c + d;

String a = new StringBuilder(b).append(c).append(d).toString();

对于大字符串来说效率要高得多.据我所知,使用concat方法时不会发生这种情况.

但是,在将空String连接到现有String时,concat方法更有效.在这种情况下,JVM不需要创建新的String对象,只需返回现有的对象即可.请参阅concat文档以确认这一点.

因此,如果您非常关注效率,那么在连接可能为空的字符串时应使用concat方法,否则使用+.但是,性能差异应该可以忽略不计,您可能不应该担心这一点.


事实上它确实如此.查看concat代码的第一行.concat的问题是它总是生成一个新的String()

3> ckpwong..:

我运行了与@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次,每次运行100k次.结果如下:

StringBuilder赢得胜利.大多数运行的时钟时间结果为0,最长时间为16ms.

a += b 每次运行大约需要40000毫秒(40秒).

concat 每次运行只需要10000毫秒(10秒).

我没有反编译该类以查看内部或通过探查器运行它,但我怀疑a += b大部分时间都在创建新对象StringBuilder然后将它们转换回来String.


对象创建时间确实很重要.这就是为什么在很多情况下我们直接使用StringBuilder而不是利用后面的StringBuilder +.

4> Paweł Adamsk..:

这里的大多数答案都是从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



5> Jason Cohen..:

Tom正确描述了+运算符的作用.它创建一个临时的StringBuilder,附加部分,并完成toString().

但是,到目前为止,所有答案都忽略了HotSpot运行时优化的效果.具体而言,这些临时操作被识别为通用模式,并在运行时被更高效的机器代码替换.

@marcio:你创造了一个微观基准 ; 使用现代JVM,这不是分析代码的有效方法.

运行时优化很重要的原因是,一旦HotSpot开始运行,代码中的许多差异 - 甚至包括对象创建 - 都会完全不同.确切知道的唯一方法是在原位分析代码.

最后,所有这些方法实际上都非常快.这可能是过早优化的情况.如果你有很多代码连接字符串的代码,那么获得最大速度的方法可能与你选择的运算符无关,而是与你正在使用的算法有关!


分析代码.

6> Marcio Aguia..:

一些简单的测试怎么样?使用下面的代码:

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"代码中优化字符串创建,知道它总是产生相同的字符串?它可以避免新的字符串创建.如果您不相信上述陈述,请测试您的自我.



7> Deepak Sharm..:

基本上,+和concat方法之间有两个重要区别。

    如果使用concat方法,则只能连接字符串,而使用+运算符时,还可以将字符串与任何数据类型连接。

    例如:

    String s = 10 + "Hello";
    

    在这种情况下,输出应为10Hello

    String s = "I";
    String s1 = s.concat("am").concat("good").concat("boy");
    System.out.println(s1);
    

    在上述情况下,您必须提供两个必需的字符串。

    +concat之间的第二个主要区别是:

    情况1: 假设我以这种方式用concat运算符连接相同的字符串

    String s="I";
    String s1=s.concat("am").concat("good").concat("boy");
    System.out.println(s1);
    

    在这种情况下,在池中创建的对象总数为7,如下所示:

    I
    am
    good
    boy
    Iam
    Iamgood
    Iamgoodboy
    

    情况2:

    现在,我将通过+运算符来隐藏相同的字符串

    String s="I"+"am"+"good"+"boy";
    System.out.println(s);
    

    在上述情况下,创建的对象总数仅为5。

    实际上,当我们通过+运算符对字符串进行隐藏时,它会维护一个StringBuffer类来执行相同的任务,如下所示:

    StringBuffer sb = new StringBuffer("I");
    sb.append("am");
    sb.append("good");
    sb.append("boy");
    System.out.println(sb);
    

    这样,它将仅创建五个对象。

所以,这些是+concat方法之间的基本区别。请享用 :)

推荐阅读
Gbom2402851125
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有