java - StringBuilder vs.concat vs. "+"运算符在 eclipse 中的相对性能与命令行不同?

标签 java eclipse jvm

我正在阅读有关 Java 编译器如何在可能的情况下将与“+”运算符连接的字符串编译为 StringBuilder 实例的信息,以及这如何使使用简单的“+”运算符更好,因为它们 compile to the same code . (除非您在 while 循环中构建字符串,在这种情况下显然最好使用 StringBuilder。)

我还读到字符串上的 .concat 方法是最差的 choice all the time (以至于它被 Findbugs 变成了一个错误!)。

所以我决定自己在 eclipse 中编写一个小的 java 类来测试它。我的结果让我有点吃惊。我发现,如果我在 eclipse 中与在命令行中遵守并运行它们,则不同的方法相对更快或更慢。

首先我的 eclipse 结果是:

the total millis to concatenate with + was: 12154
the total millis to concatenate with .concat was: 8840
the total millis to concatenate with StringBuilder was: 11350
the total millis to concatenate with StringBuilder with a specified size was: 5611

因此在 eclipse 中,指定大小的 StringBuilder 最快,其次是 .concat(奇怪),然后 StringBuilder 和“+”连接几乎相同。

然而,我在命令行上的结果是:

the total millis to concatenate with + was: 4139
the total millis to concatenate with .concat was: 8590
the total millis to concatenate with StringBuilder was: 10888
the total millis to concatenate with StringBuilder with a specified size was: 6033

因此,当我从命令行编译和运行时,“+”运算符显然是最快的,其次是带大小的 String 构建器,然后是 concat,最后是普通的 StringBuilder!

这对我来说没有意义。显然,我读到的所有 stackoverflow 答案都说 + 运算符编译成普通的旧 StringBuilder 实例必须过时。

有人知道这里到底发生了什么吗?

我使用的是 jdk1.7.0_07,据我所知,eclipse 和我的命令行引用的是完全相同的一个。我知道的唯一区别是 eclipse 使用的是“javaw”,但从我读过的内容来看,这应该没有什么区别。

如果您想验证我没有做错任何事情,这是我的测试类,但我很确定它是可靠的。

public class Test {

    static final int LOOPS = 100000000;
    static final String FIRST_STRING = "This is such";
    static final String SECOND_STRING = " an awesomely cool ";
    static final String THIRD_STRING = "to write string.";

    /**
     * @param args
     */
    public static void main(String[] args) {

        Test.plusOperator();
        Test.dotConcat();
        Test.stringBuilder();
        Test.stringBuilderSizeSpecified();

    }

    public static void plusOperator() {
        String localOne = FIRST_STRING;
        String localTwo = SECOND_STRING;
        String localThree = THIRD_STRING;

        Calendar startTime = Calendar.getInstance();
        for (int x = 0; x < LOOPS; x++) {
            String toPrint = localOne + localTwo + localThree;
        }
        Calendar endTime = Calendar.getInstance();
        System.out.println("the total millis to concatenate with + was: " + 
                (endTime.getTimeInMillis() - startTime.getTimeInMillis()));
    }

    public static void stringBuilder() {
        String localOne = FIRST_STRING;
        String localTwo = SECOND_STRING;
        String localThree = THIRD_STRING;

        Calendar startTime = Calendar.getInstance();
        for (int x = 0; x < LOOPS; x++) {
            StringBuilder toBuild = new StringBuilder()
                .append(localOne)
                .append(localTwo)
                .append(localThree);
        }
        Calendar endTime = Calendar.getInstance();
        System.out.println("the total millis to concatenate with StringBuilder was: " + 
                (endTime.getTimeInMillis() - startTime.getTimeInMillis()));
    }

    public static void stringBuilderSizeSpecified() {
        String localOne = FIRST_STRING;
        String localTwo = SECOND_STRING;
        String localThree = THIRD_STRING;

        Calendar startTime = Calendar.getInstance();
        for (int x = 0; x < LOOPS; x++) {
            StringBuilder toBuild = new StringBuilder(50)
                .append(localOne)
                .append(localTwo)
                .append(localThree);
        }
        Calendar endTime = Calendar.getInstance();
        System.out.println("the total millis to concatenate with StringBuilder with a specified size was: " + 
                (endTime.getTimeInMillis() - startTime.getTimeInMillis()));
    }

    public static void dotConcat() {
        String localOne = FIRST_STRING;
        String localTwo = SECOND_STRING;
        String localThree = THIRD_STRING;

        Calendar startTime = Calendar.getInstance();
        for (int x = 0; x < LOOPS; x++) {
            String toPrint = localOne.concat(localTwo).concat(localThree);
        }
        Calendar endTime = Calendar.getInstance();
        System.out.println("the total millis to concatenate with .concat was: " + 
                (endTime.getTimeInMillis() - startTime.getTimeInMillis()));
    }

}

最佳答案

在 Oracle JDK 1.7 (javac 1.7.0_17) 上,“+”运算符仍然使用 StringBuilder 实现,如在类上运行 javap -c 所示获取字节码(这里只显示循环):

public static void plusOperator();
Code:

  16: iload         4
  18: ldc           #10                 // int 100000000
  20: if_icmpge     53
  23: new           #11                 // class java/lang/StringBuilder
  26: dup           
  27: invokespecial #12                 // Method java/lang/StringBuilder."<init>":()V
  30: aload_0       
  31: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  34: aload_1       
  35: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  38: aload_2       
  39: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  42: invokevirtual #14                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  45: astore        5
  47: iinc          4, 1
  50: goto          16


public static void stringBuilder();
Code:

  16: iload         4
  18: ldc           #10                 // int 100000000
  20: if_icmpge     50
  23: new           #11                 // class java/lang/StringBuilder
  26: dup           
  27: invokespecial #12                 // Method java/lang/StringBuilder."<init>":()V
  30: aload_0       
  31: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  34: aload_1       
  35: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  38: aload_2       
  39: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  42: astore        5
  44: iinc          4, 1
  47: goto          16

这两者之间的唯一区别是带有“+”的版本在循环中将 StringBuilder 转换为 String

所以问题就变成了:为什么您的测试对于相同的代码会显示如此不同的结果。或者更完整地说,为什么这不是一个有效的微基准测试。以下是一些可能的原因:

  • 您正在计算挂钟时间。这意味着您实际上是在测量 JVM 在运行测试时所做的一切。其中包括垃圾收集(这很重要,因为您正在制造大量垃圾)。您可以通过获取线程 CPU 时间来缓解这种情况。
  • 您没有验证 HotSpot 何时或是否正在编译这些方法。这就是为什么您应该在任何微基准测试之前进行预热阶段:基本上,在运行实际测试之前多次运行 main()

关于java - StringBuilder vs.concat vs. "+"运算符在 eclipse 中的相对性能与命令行不同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15397515/

相关文章:

c++ - 在 Eclipse/StatET 中编写包含 C++ 代码的 R 包

java - 优化java循环内的空检查?

java - 阅读 Core Java 第 11 版时关于 **类型删除机制** 的问题

eclipse - 无法让 IntelliJ 识别 proto 编译的 Java 类文件

安卓 OCR : AndrOCR source code error

java - 有哪些 Java 实用程序可用于对机器的 CPU、内存、磁盘和网络 I/O 性能进行基准测试?

java - 添加两个数组列表的问题

java - 如何合并几个列表?

java - 获取环绕 Socket.getOutputStream 的 DataOutputStream 写入的字节数

java - 尝试猜测多少次