前天写了一篇文章Java性能陷阱:StringBuffer的性能真的比String好吗?,有两位网友进行了评论,而且看到得到了三张反对票,因此感觉有必要进行更深入的说明和研究。针对网友评论中提到的字符串直接连接操作会产生更多的垃圾对象的问题并联想到那篇文章的缘起文章(使用String还是StringBuffer?(cherami))中提到的反编译类文件的技巧,感觉可以实际的验证一下,因此又做了如下的测试。
第一步,准备工作 为了方便后面测试工作的进行,有必要编写一个简单的脚本: echo test by jdk1.2.2 /opt/java/jdk1.2.2/bin/javac StringCompileTest.java /opt/java/jdk1.2.2/bin/javap -c StringCompileTest echo test by jdk1.3.1_09 /opt/java/jdk1.3.1_09/bin/javac StringCompileTest.java /opt/java/jdk1.3.1_09/bin/javap -c StringCompileTest echo test by jdk1.4.2 /opt/java/jdk1.4.2/bin/javac StringCompileTest.java /opt/java/jdk1.4.2/bin/javap -c StringCompileTest
上面的脚本根据需要可以应用在windows或者linux上,我是在linux进行测试的,因此我把它保存为一个文件stringdecompile.sh,如果你在windows上测试,你可以保存为stringdecompile.bat。
第二步,测试代码
- public class StringCompileTest {
- java/lang/String.java.html" target="_blank">String s1="This is a ";
- java/lang/String.java.html" target="_blank">String s2="string decompile ";
- java/lang/String.java.html" target="_blank">String s3="testing.";
- public void stringTest() {
- java/lang/String.java.html" target="_blank">String s=s1+s2+s3;
- }
- public void stringBufferTest() {
- java/lang/StringBuffer.java.html" target="_blank">StringBuffer buffer=new java/lang/StringBuffer.java.html" target="_blank">StringBuffer();
- buffer.append(s1);
- buffer.append(s2);
- buffer.append(s3);
- java/lang/String.java.html" target="_blank">String s=buffer.toString();
- }
- }
运行结果: test by jdk1.2.2 Compiled from StringCompileTest.java public class StringCompileTest extends java.lang.Object { java.lang.String s1; java.lang.String s2; java.lang.String s3; public StringCompileTest(); public void stringBufferTest(); public void stringTest(); } Method StringCompileTest() 0 aload_0 1 invokespecial #8 <Method java.lang.Object()> 4 aload_0 5 ldc #1 <String "This is a "> 7 putfield #12 <Field java.lang.String s1> 10 aload_0 11 ldc #2 <String "string decompile "> 13 putfield #13 <Field java.lang.String s2> 16 aload_0 17 ldc #3 <String "testing."> 19 putfield #14 <Field java.lang.String s3> 22 return Method void stringBufferTest() 0 new #7 <Class java.lang.StringBuffer> 3 dup 4 invokespecial #9 <Method java.lang.StringBuffer()> 7 astore_1 8 aload_1 9 aload_0 10 getfield #12 <Field java.lang.String s1> 13 invokevirtual #11 <Method java.lang.StringBuffer append(java.lang.String)> 16 pop 17 aload_1 18 aload_0 19 getfield #13 <Field java.lang.String s2> 22 invokevirtual #11 <Method java.lang.StringBuffer append(java.lang.String)> 25 pop 26 aload_1 27 aload_0 28 getfield #14 <Field java.lang.String s3> 31 invokevirtual #11 <Method java.lang.StringBuffer append(java.lang.String)> 34 pop 35 aload_1 36 invokevirtual #15 <Method java.lang.String toString()> 39 astore_2 40 return Method void stringTest() 0 new #7 <Class java.lang.StringBuffer> 3 dup 4 aload_0 5 getfield #12 <Field java.lang.String s1> 8 invokestatic #16 <Method java.lang.String valueOf(java.lang.Object)> 11 invokespecial #10 <Method java.lang.StringBuffer(java.lang.String)> 14 aload_0 15 getfield #13 <Field java.lang.String s2> 18 invokevirtual #11 <Method java.lang.StringBuffer append(java.lang.String)> 21 aload_0 22 getfield #14 <Field java.lang.String s3> 25 invokevirtual #11 <Method java.lang.StringBuffer append(java.lang.String)> 28 invokevirtual #15 <Method java.lang.String toString()> 31 astore_1 32 return test by jdk1.3.1_09 Compiled from StringCompileTest.java public class StringCompileTest extends java.lang.Object { java.lang.String s1; java.lang.String s2; java.lang.String s3; public StringCompileTest(); public void stringTest(); public void stringBufferTest(); } Method StringCompileTest() 0 aload_0 1 invokespecial #1 <Method java.lang.Object()> 4 aload_0 5 ldc #2 <String "This is a "> 7 putfield #3 <Field java.lang.String s1> 10 aload_0 11 ldc #4 <String "string decompile "> 13 putfield #5 <Field java.lang.String s2> 16 aload_0 17 ldc #6 <String "testing."> 19 putfield #7 <Field java.lang.String s3> 22 return Method void stringTest() 0 new #8 <Class java.lang.StringBuffer> 3 dup 4 invokespecial #9 <Method java.lang.StringBuffer()> 7 aload_0 8 getfield #3 <Field java.lang.String s1> 11 invokevirtual #10 <Method java.lang.StringBuffer append(java.lang.String)> 14 aload_0 15 getfield #5 <Field java.lang.String s2> 18 invokevirtual #10 <Method java.lang.StringBuffer append(java.lang.String)> 21 aload_0 22 getfield #7 <Field java.lang.String s3> 25 invokevirtual #10 <Method java.lang.StringBuffer append(java.lang.String)> 28 invokevirtual #11 <Method java.lang.String toString()> 31 astore_1 32 return Method void stringBufferTest() 0 new #8 <Class java.lang.StringBuffer> 3 dup 4 invokespecial #9 <Method java.lang.StringBuffer()> 7 astore_1 8 aload_1 9 aload_0 10 getfield #3 <Field java.lang.String s1> 13 invokevirtual #10 <Method java.lang.StringBuffer append(java.lang.String)> 16 pop 17 aload_1 18 aload_0 19 getfield #5 <Field java.lang.String s2> 22 invokevirtual #10 <Method java.lang.StringBuffer append(java.lang.String)> 25 pop 26 aload_1 27 aload_0 28 getfield #7 <Field java.lang.String s3> 31 invokevirtual #10 <Method java.lang.StringBuffer append(java.lang.String)> 34 pop 35 aload_1 36 invokevirtual #11 <Method java.lang.String toString()> 39 astore_2 40 return test by jdk1.4.2 Compiled from "StringCompileTest.java" public class StringCompileTest extends java.lang.Object{ java.lang.String s1; java.lang.String s2; java.lang.String s3; public StringCompileTest(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: aload_0 5: ldc #2; //String This is a 7: putfield #3; //Field s1:Ljava/lang/String; 10: aload_0 11: ldc #4; //String string decompile 13: putfield #5; //Field s2:Ljava/lang/String; 16: aload_0 17: ldc #6; //String testing. 19: putfield #7; //Field s3:Ljava/lang/String; 22: return public void stringTest(); Code: 0: new #8; //class StringBuffer 3: dup 4: invokespecial #9; //Method java/lang/StringBuffer."<init>":()V 7: aload_0 8: getfield #3; //Field s1:Ljava/lang/String; 11: invokevirtual #10; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 14: aload_0 15: getfield #5; //Field s2:Ljava/lang/String; 18: invokevirtual #10; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 21: aload_0 22: getfield #7; //Field s3:Ljava/lang/String; 25: invokevirtual #10; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 28: invokevirtual #11; //Method java/lang/StringBuffer.toString:()Ljava/lang/String; 31: astore_1 32: return public void stringBufferTest(); Code: 0: new #8; //class StringBuffer 3: dup 4: invokespecial #9; //Method java/lang/StringBuffer."<init>":()V 7: astore_1 8: aload_1 9: aload_0 10: getfield #3; //Field s1:Ljava/lang/String; 13: invokevirtual #10; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 16: pop 17: aload_1 18: aload_0 19: getfield #5; //Field s2:Ljava/lang/String; 22: invokevirtual #10; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 25: pop 26: aload_1 27: aload_0 28: getfield #7; //Field s3:Ljava/lang/String; 31: invokevirtual #10; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 34: pop 35: aload_1 36: invokevirtual #11; //Method java/lang/StringBuffer.toString:()Ljava/lang/String; 39: astore_2 40: return }
分析 我想我们没有必要仔细的进行分析了,稍稍对比一下,我们就可以看到对于JDK1.3.1和JDK1.4.2而言,编译器在编译代码的时候已经进行了优化,而且实际上字符串直接操作产生的最终代码(操作步骤)其实比用StringBuffer的更短,这就解释了为什么对于JDK1.3.1和JDK1.4.2而言,在次数比较少的情况下,字符串直接操作比StringBuffer的性能实际上更好。 但是问题又来了,那么为什么在次数很多的情况或者进行累加的时候,StringBuffer的性能要好或者好很多呢。我想问题的核心在于StringBuffer多出的那几条指令上,初步看了一下JVM规范对于JVM指令助记符的说明得知那几条多出的只能是对于堆栈的操作,这个可以大致的说明为什么StringBuffer的性能好一些,因为它是进行堆栈的存取,速度可能比内存堆的速度快。至于问题的最终的完整解释需要等我这周看完相关的助记符的意义以后才能有个结果。
总结 从我们实验的结果看,我们最起码可以得到如下的结论: 对于一条字符串直接连接操作而言,它并没有产生中间多余的对象,同样编译器在进行处理的时候将之转换为StringBuffer进行处理了。
但是我感觉可能是我在那篇文章里面对于测试的目的没有说得很清楚,而且最终的结论里面也说得不够完整, 对于累加的情况,确实会产生更多的对象,但是我为了将一条语句执行的时间放大才放在一个循环里面,在实际应用中我想没有谁会那么用。cherami的文章最后得到的结论--如果你对字符串中的内容经常进行操作,特别是内容要修改时,那么使用StringBuffer--依然是成立的,我担心的是那篇文章对String和StringBuffer使用的另外一方面的误导:由于担心性能问题对单条语句也使用StringBuffer进行替换。
从这次的测试我们还可以更深切的感受到java编译器和JVM或者JRE在不断进行着提高,相信以后性能方面的担忧会更少。由于JDK最初的一些版本,甚至是到了JDK1.2.2还没有解决一些问题而导致网上有很多讨论java性能的问题,但是这些问题对于以后的JDK版本而言可能已经不是问题了。 |
|