我们经常会看到或者听到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
}
持续更新