StringBuilder vs. .concat vs. “+"eclipse中的操作员相对性能与命令行不同?
我正在阅读有关 java 编译器如何在可能的情况下将与+"运算符连接的字符串编译为 StringBuilder 的实例,以及如何更好地使用简单的+"运算符,因为它们 编译成相同的代码.(除非您在 while 循环中构建字符串,在这种情况下显然最好使用 StringBuilder.)
I was reading about how when possible the java compiler will compile strings concatenated with the "+" operator into instances of StringBuilder, and how this makes it better to use the simple "+" operator since they compile to the same code. (Except when you are building the string in a while loop, in which case it is clearly best to use a StringBuilder.)
我还了解到,字符串上的 .concat 方法一直是最糟糕的选择(以至于它被 Findbugs 变成了一个错误!).
I've also read that the .concat method on strings is the worst choice all the time (so much so that it was made into a bug by Findbugs!).
所以我决定自己在eclipse中编写一个小的java类来测试它.我的结果让我有点吃惊.我发现,如果我在 Eclipse 和命令行中编译并运行它们,不同的方法相对更快或更慢.
So I decided to test it myself writing a little java class in eclipse. My results surprised me a bit. What I found was that different methods were relatively faster or slower if I complied and ran them in eclipse vs. on the command line.
首先我的日食结果是:
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和+"连接几乎相同.
So in eclipse StringBuilder with the size specified was fastest, followed by .concat (weird), then StringBuilder and "+" concatenation were pretty much the same.
然而,我在命令行上的结果是:
My results on the command line, however, were:
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 builder,然后是 concat,最后是普通 StringBuilder!
So when I compiled and ran from the commnad line the "+" operator was clearly the fastest, followed by String builder with size, then concat, and last was normal StringBuilder!
这对我来说没有意义.显然,我读到的所有 stackoverflow 答案都说 + 运算符编译成普通的旧 StringBuilder 实例必须是过时的.
This doesn't make sense to me. Obviously all the stackoverflow answers I read saying that + operators compile into normal old StringBuilder instances must be outdated.
有人知道这里到底发生了什么吗?
Does anyone know what's really going on here?
我正在使用 jdk1.7.0_07,据我所知,eclipse 和我的命令行都引用了完全相同的一个.我知道的唯一区别是 eclipse 是使用javaw",但从我读过的内容来看,这应该没什么区别.
I'm using jdk1.7.0_07, and so far as I can tell both eclipse and my command line are referencing the exact same one. The only difference I know of is eclipse is using "javaw", but from what I've read, that shouldn't make a difference.
如果你想验证我没有做错什么,这是我的测试课程,但我很确定它是可靠的.
Here's my test class if you want to verify I'm not doing anything wrong, but I'm pretty sure it's solid.
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
在类上获取字节码(这里只显示循环):
On Oracle JDK 1.7 (javac 1.7.0_17), the "+" operator is still implemented using StringBuilder
, as shown by running javap -c
on the class to get the bytecode (only showing the loops here):
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
.
The only difference between these two is that the version with "+" converts the StringBuilder
to a String
within the loop.
所以问题变成了:为什么您的测试会针对相同的代码显示如此不同的结果.或者更完整地说,为什么这不是一个有效的微基准测试.以下是一些可能的原因:
So the question becomes: why does your test show such different results for the same code. Or more completely, why is this not a valid micro-benchmark. Here are some possible reasons:
- 您正在计算挂钟时间.这意味着您实际上是在测量 JVM 在运行测试时所做的一切.其中包括垃圾收集(这很重要,因为您正在创建大量垃圾).您可以通过获取线程 CPU 时间来缓解这种情况.
- 您无法验证 HotSpot 何时或是否正在编译方法.这就是为什么您应该在任何微基准测试之前进行预热阶段:基本上,在运行实际测试之前多次运行
main()
.
相关文章