StringBuilder vs. .concat vs.“+”运算符相对性能在eclipse中与命令行不同?

时间:2013-03-13 22:12:52

标签: java eclipse jvm

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

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

所以我决定自己在eclipse中编写一个java类来测试它。我的结果让我感到有些惊讶。我发现如果我在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

所以当我从commnad行编译并运行时,“+”运算符显然是最快的,接着是带有大小的String builder,然后是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()));
    }

}

2 个答案:

答案 0 :(得分:12)

在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()

答案 1 :(得分:1)

尝试将StringBuilder toBuild = new StringBuilder()置于循环之上。对于字符串String toPrint+=也一样,您会看到差异 不要在循环中创建新的String和StringBuilder。