Java的。连接字符串。微基准

时间:2011-10-23 19:38:53

标签: java string microbenchmark

在第一步,我运行此代码:

public class Demo  {
    public static void main(String[] args) {
        String x = "x";
        long start = System.currentTimeMillis();

        for (int i = 0; i < 100000; i++)

        {

            x = x.concat("s");

            // x+="k";

        }

        System.out.println(System.currentTimeMillis() - start);
    }

}

出:13579。

第二步,我运行此代码:

public class Demo {
    public static void main(String[] args) {
        String x = "x";
        long start = System.currentTimeMillis();

        for (int i = 0; i < 100000; i++)

        {

            //x = x.concat("s");

             x+="k";

        }

        System.out.println(System.currentTimeMillis() - start);
    }

}

出:27328。

我有两个问题:

  1. 我可以说我的banchmark - 对吗?
  2. 为什么(+)和concat()之间有如此大的时间线差异? 13.5秒VS 27秒为什么?

5 个答案:

答案 0 :(得分:6)

我认为你的microbenchmark很好,我可以重现你的结果。

在我的JVM上,x += "k"速度慢两倍的原因是它覆盖了以下内容:

  1. 创建新的StringBuilder;
  2. x附加到StringBuilder;
  3. "k"附加到StringBuilder;
  4. 致电StringBuilder.toString()并将结果分配给x
  5. 这会复制两次字符数据(一次在步骤2中,一次在步骤4中)。

    另一方面,x = x.concat("s")仅复制一次数据。

    这种双重复制使x += "k"比另一个版本慢两倍。

    如果您感到好奇,以下是我的编译器为+=循环生成的字节码:

       10:  goto    36
       13:  new #24; //class java/lang/StringBuilder
       16:  dup
       17:  aload_1
       18:  invokestatic    #26; //Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
       21:  invokespecial   #32; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
       24:  ldc #35; //String k
       26:  invokevirtual   #37; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       29:  invokevirtual   #41; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
       32:  astore_1
       33:  iinc    4, 1
       36:  iload   4
       38:  ldc #45; //int 100000
       40:  if_icmplt   13
    

    说明21&amp; 29是制作两份副本的地方。

答案 1 :(得分:0)

因为当您使用+连接字符串时,实际上创建了一个新的StringBuffer并使用它而不是原始字符串,这就是为什么最终可能比只使用concat时更慢。

关于正确性,最好查看字节码以查看实际情况。如果编译器知道其执行的确切结果,它可能会优化掉一些代码,而且它永远不会改变。

答案 2 :(得分:0)

现在试试这个,它会击败你们两个:

public class Demo {
    public static void main(String[] args) {
        StringBuilder x = new StringBuilder("x");
        long start = System.currentTimeMillis();

        for (int i = 0; i < 100000; i++)

        {

             x.append("k");

        }

        System.out.println(System.currentTimeMillis() - start);
    }

}

这是一个稍微优化的版本:

public class Demo {
    public static void main(String[] args) {
        StringBuilder x = new StringBuilder(100001).append('x');
        long start = System.currentTimeMillis();

        for (int i = 0; i < 100000; i++)

        {

             x.append('k');

        }

        System.out.println(System.currentTimeMillis() - start);
    }

}

答案 3 :(得分:0)

如果你知道这些是等价的,那会有用吗?

x+="k";
x = new StringBuffer(x).append("k").toString();

或者在较新的Java中(我忘记它是从1.5还是1.6)它改为使用它,因为它至少避免了StringBuffer对它造成的锁定:

x = new StringBuilder(x).append("k").toString();

随着所有内存管理的继续,毫无疑问concat(String)方法可以做得更好。另一方面,如果你要做那个循环,你最好不要在可变对象和不可变对象之间来回转换:

String x = "x";
long start = System.currentTimeMillis();

StringBuilder sb = new StringBuilder(x);
for (int i = 0; i < 100000; i++) {
    sb.append("f");
}
x = sb.toString();

System.out.println(System.currentTimeMillis() - start);

由于StringBuilder(和StringBuffer一样,但由于其他原因而效率较低)为您做智能缓冲管理,这将是一种明显更快的技术;比这更快的速度需要真正的努力(或至少预先调整缓冲区的大小,这在这种情况下是可能的,但一般来说更难)。

答案 4 :(得分:0)

如果您单步执行

等行
    String x2 = x + "x";
在Eclipse中,您将看到它创建了一个StringBuilder 帮助构造新String的对象。那是什么 发生在你的第二个案件中。

在第一种情况下,String.concat直接创建新的String 任何StringBuilder所以它会快一点。