3

第一步,我运行此代码:

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. 我可以说我的基准 - 正确吗?
  2. 为什么 (+) 和 concat() 之间的时间线差异如此之大???13.5 秒 VS 27 秒。为什么?
4

5 回答 5

6

我认为您的微基准测试很好,我可以重现您的结果。

在我的 JVM 上,x += "k"速度慢两倍的原因是它在幕后做了以下事情:

  1. 创建一个新的StringBuilder
  2. 附加xStringBuilder;
  3. 附加"k"StringBuilder;
  4. 调用StringBuilder.toString()并将结果分配给x.

这会将字符数据复制两次(一次在步骤 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 和 29 是制作两份副本的地方。

于 2011-10-23T19:47:47.910 回答
0

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

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

于 2011-10-23T19:43:26.033 回答
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);
    }

}
于 2011-10-23T19:53:19.677 回答
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);

既然StringBuilderStringBuffer发生这种情况,但由于其他原因效率较低)为您执行智能缓冲区管理,这将是一种明显更快的技术;比这更快需要真正的努力(或者至少预先调整缓冲区的大小,在这种情况下这是可能的,但通常更难)。

于 2011-10-23T19:55:47.733 回答
0

如果您单步执行一条线,例如

    String x2 = x + "x";

在 Eclipse 中,您将看到它创建了一个StringBuilder 对象来帮助构建新的String. 这就是在你的第二种情况下发生的事情。

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

于 2011-10-23T19:56:34.947 回答