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

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

我还读到字符串上的.concat方法一直是最糟糕的选择 (以至于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构建器,然后是concat,最后是正常的StringBuilder!

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

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

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

这是我的测试课,如果你想validation我没有做错什么,但我很确定它是可靠的。

 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."":()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."":()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时间来缓解此问题。
  • 您不validationHotSpot何时或是否正在编译方法。 这就是为什么你应该在任何微基准之前做一个预热阶段:基本上,在你运行实际测试之前多次运行你的main()

尝试在循环上方放置StringBuilder toBuild = new StringBuilder() 。 与String toPrint相同,对字符串执行+= ,您将看到差异。
不要在循环中创建新的String和StringBuilder。