代码之家  ›  专栏  ›  技术社区  ›  shsteimer

字符串串联:concat()与“+”运算符

  •  439
  • shsteimer  · 技术社区  · 16 年前

    假设字符串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);
        }
    }
    
    11 回复  |  直到 6 年前
        1
  •  510
  •   Tom Hawtin - tackline    7 年前

    不,不完全是这样。

    首先,语义学上有点不同。如果 a null 然后 a.concat(b) 抛出一个 NullPointerException 但是 a+=b 将处理的原始值 仿佛它是 无效的 . 而且, concat() 方法只接受 String 价值观而 + 运算符将静默地将参数转换为字符串(使用 toString() 对象的方法)。所以 连接() 方法对它接受的内容更严格。

    要查看引擎盖下面,请编写一个简单的类 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中提供。您可以看到您正在构建一个char数组(根据需要调整大小),然后在创建final时丢弃它。 . 实际上,内存分配速度惊人。

    更新: 正如PawelAdamski指出的,性能在最近的热点已经发生了变化。 javac 仍然产生完全相同的代码,但字节码编译器欺骗。简单的测试完全失败,因为整个代码体都被丢弃了。求和 System.identityHashCode (不是) String.hashCode 显示 StringBuffer 代码有一点优势。当下一个更新发布时,或者如果您使用不同的JVM,则可能发生更改。从 @lukaseder , a list of HotSpot JVM intrinsics .

        2
  •  83
  •   Community Mr_and_Mrs_D    7 年前

    Niyaz 是正确的,但也值得注意的是,特殊的+运算符可以被Java编译器转换成更有效率的东西。Java有一个String Bu建器类,它表示一个非线程安全的、可变的字符串。当执行串串连接时,Java编译器悄悄地转换。

    String a = b + c + d;
    

    进入之内

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

    对于大字符串来说,这样做效率更高。据我所知,使用concat方法时不会发生这种情况。

    但是,当将空字符串连接到现有字符串时,concat方法更有效。在这种情况下,JVM不需要创建新的字符串对象,只需返回现有的字符串对象即可。见 the concat documentation 以确认这一点。

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

        3
  •  44
  •   ckpwong    16 年前

    我做了一个类似于@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,最长的为16毫秒。
    • a += b 每次运行大约需要40000ms(40s)。
    • concat 每次运行只需要10000ms(10s)。

    我还没有对类进行反编译以查看内部结构或通过分析器运行它,但我怀疑 A+B 花费大量时间创建 字符串拼接 然后把它们转换回 String .

        4
  •  22
  •   Jason Cohen    16 年前

    汤姆准确地描述了+运算符的作用。它创建了一个临时的 StringBuilder ,附加零件,并用 toString() .

    然而,到目前为止,所有的答案都忽略了热点运行时优化的影响。具体来说,这些临时操作被认为是一种常见的模式,并在运行时被更高效的机器代码所取代。

    @马西奥:你创造了一个 micro-benchmark ;对于现代JVM,这不是分析代码的有效方法。

    运行时优化之所以重要,是因为一旦热点出现,代码中的许多差异——甚至包括对象创建——都是完全不同的。唯一确定的方法是分析代码 就地 .

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

        5
  •  21
  •   acdcjunior Mukul Kumar    11 年前

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

    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() 版本执行平均花费了一半的时间。

    这个结果让我吃惊,因为 连接() 方法始终创建一个新字符串(它返回一个 new String(result) “。众所周知:

    String a = new String("a") // more than 20 times slower than String a = "a"
    

    为什么编译器不能优化“a+b”代码中的字符串创建,因为知道它总是产生相同的字符串?它可以避免创建新的字符串。 如果你不相信上面的说法,那就测试一下你自己。

        6
  •  20
  •   Paweł Adamski    7 年前

    大多数答案来自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
    
        7
  •  4
  •   Peter Mortensen Mohit    7 年前

    基本上,+和 concat 方法。

    1. 如果您正在使用 康塔特 方法,则只能在 + 运算符,还可以将字符串与任何数据类型连接起来。

      例如:

      String s = 10 + "Hello";
      

      在这种情况下,输出应该是 10你好 .

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

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

    2. 第二个和主要的区别 + 康塔特 是:

      案例1: 假设我用 康塔特 以这种方式操作

      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);
      

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

    伙计们,这是 + 以及 康塔特 方法。 享受:

        8
  •  2
  •   dingalapadum    7 年前

    为了完整起见,我想补充一下“+”运算符的定义可以在 JLS SE8 15.18.1 :

    如果只有一个操作数表达式是字符串类型,则字符串 对另一个操作数执行转换(§5.1.11),以生成 运行时的字符串。

    字符串串联的结果是对字符串对象的引用 这是两个操作数字符串的串联。人物形象 左边的操作数在右边的字符之前 新创建字符串中的操作数。

    字符串对象是新创建的(§12.5),除非表达式是 常量表达式(§15.28)。

    关于实施,JLS表示:

    一个实现可以选择执行转换和连接 在一个步骤中避免创建然后丢弃中间 字符串对象。提高重复字符串的性能 级联,Java编译器可以使用StringBuffer类或 减少中间字符串对象数量的类似技术 通过表达式的计算创建的。

    对于基元类型,实现还可以优化 通过直接从基元转换来创建包装对象 键入字符串。

    因此,从“Java编译器可以使用StringBuffer类或类似的技术来减少”,不同的编译器可以产生不同的字节码。

        9
  •  2
  •   Dimitar    7 年前

    这个 +算子 可以在字符串和string、char、integer、double或float数据类型值之间工作。它只是在串联之前将值转换为字符串表示形式。

    这个 康塔特算子 只能对字符串执行。它检查数据类型的兼容性,如果不匹配则抛出一个错误。

    除此之外,您提供的代码执行相同的操作。

        10
  •  2
  •   Adrien Brunelat    6 年前

    我不这么认为。

    a.concat(b) 在String中实现,我认为自从早期Java机器以来,实现没有太大变化。这个 + 操作实现依赖于Java版本和编译器。目前 + 是使用 StringBuffer 使操作尽可能快。也许在将来,这会改变。在早期版本的Java中 + 对字符串的操作要慢得多,因为它产生了中间结果。

    我想 += 是使用 + 同样优化。

        11
  •  0
  •   Peter Mortensen Mohit    7 年前

    当使用+时,速度会随着字符串长度的增加而降低,但当使用concat时,速度更稳定,最佳选择是使用具有稳定速度的StringBuilder类来实现这一点。

    我想你能理解为什么。但是创建长字符串的最好方法是使用StringBuilder()和Append(),这两种速度都是不可接受的。