我们经常会看到或者听到java代码要如何如何优化,这样写不对,那样写性能不高等等,可是我们如何去伪求真呢?就以 Java 代码性能优化总结文章中的内容例子,来求证一下吧
先热热身
String字符串拼接和StringBuffer一样吗
看看下面这段代码:
public class TestClass{ public String incStringJoint(){ String str = "a"; String str1 = str + "b"; return str1; } public String incStringBuffer(){ StringBuffer str = new StringBuffer(3); str.append("a"); str.append("b"); return str.toString(); } }
抛开其他方面,这两段代码性能一样吗? 带着这个问题,我们去探索一番。
javac TestClass.java //将java文件编译成class文件 javap -verbose TestClass.class //反编译class文件
反编译结果:
{ public TestClass(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 public java.lang.String incStringJoint(); // incStringJoint()方法执行情况 descriptor: ()Ljava/lang/String; flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: ldc #2 // String a 2: astore_1 3: new #3 // class java/lang/StringBuilder 6: dup 7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 10: aload_1 11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 14: ldc #6 // String b 16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 22: astore_2 23: aload_2 24: areturn LineNumberTable: //反编译结果对应源码的行数 line 4: 0 line 5: 3 line 6: 23 public java.lang.String incStringBuffer(); // incStringBuffer()方法执行情况 descriptor: ()Ljava/lang/String; flags: ACC_PUBLIC Code: stack=3, locals=2, args_size=1 0: new #8 // class java/lang/StringBuffer 3: dup 4: iconst_3 5: invokespecial #9 // Method java/lang/StringBuffer."<init>":(I)V 8: astore_1 9: aload_1 10: ldc #2 // String a 12: invokevirtual #10 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 15: pop 16: aload_1 17: ldc #6 // String b 19: invokevirtual #10 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 22: pop 23: aload_1 24: invokevirtual #11 // Method java/lang/StringBuffer.toString:()Ljava/lang/String; 27: areturn LineNumberTable: //反编译结果对应源码的行数 line 10: 0 line 11: 9 line 12: 16 line 13: 23 } SourceFile: "TestClass.java"
我们先大致看一下反编译的结果,可以发现其实String字符串拼接实质上是用StringBuilder的append方法实现,那么我们再看一下StringBuffer和StringBuilder的源码:可以发现,两个类的append方法调用的是同一个方法实现字符串拼接。
public AbstractStringBuilder append(String str) { if (str == null) return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; }
这里就有一个问题了,那为什么在字符串拼接的时候,要使用StringBuffer(线程安全)或者使用StringBuilder(线程不安全)呢?
现在我们修改一下代码:
public String incStringJoint(){ String str = "a"; String str1 = str + "b"; String str2 = str1 + "c"; return str2; }
多次拼接,看看结果怎么?
public java.lang.String incStringJoint(); descriptor: ()Ljava/lang/String; flags: ACC_PUBLIC Code: stack=2, locals=4, args_size=1 0: ldc #2 // String a 2: astore_1 3: new #3 // class java/lang/StringBuilder 6: dup 7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 10: aload_1 11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 14: ldc #6 // String b 16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 22: astore_2 23: new #3 // class java/lang/StringBuilder 26: dup 27: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 30: aload_2 31: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 34: ldc #8 // String c 36: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 39: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 42: astore_3 43: aload_3 44: areturn LineNumberTable: line 4: 0 line 5: 3 line 6: 23 line 7: 43
注意第3行和第23行,new了两个StringBuilder。这就说明,利用String本身进行字符串拼接的时候,每拼接一次,就要new一个StringBuilder,再调用append方法。
new String()到底创建了几个对象
看下面代码:
public class TestClass{ public String incString(){ String str = "a"; return str; } public String incNewString(){ String str = new String("a"); return str; } } { public TestClass(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 public java.lang.String incString(); descriptor: ()Ljava/lang/String; flags: ACC_PUBLIC Code: stack=1, locals=2, args_size=1 0: ldc #2 // String a 2: astore_1 3: aload_1 4: areturn LineNumberTable: line 20: 0 line 21: 3 public java.lang.String incNewString(); descriptor: ()Ljava/lang/String; flags: ACC_PUBLIC Code: stack=3, locals=2, args_size=1 0: new #3 // class java/lang/String 3: dup 4: ldc #2 // String a 6: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V 9: astore_1 10: aload_1 11: areturn LineNumberTable: line 25: 0 line 26: 10 }
持续更新