StringBuffer、StringBuilder與String剖析

1 StringBuffer與StringBuilder

StringBufferStringBuilder都是final類,不能被繼承。


上圖是二者的UML圖,可以看出,它們都繼承了抽象類AbstractBuilderAbstractBuilder定義了StringBufferStringBuilder的基本操作。

這是append方法實現代碼:

 public AbstractStringBuilder append(String str) {
	if (str == null) str = "null";
        int len = str.length();
	if (len == 0) return this;
	int newCount = count + len;
	if (newCount > value.length)
	    expandCapacity(newCount);
	str.getChars(0, len, value, count);
	count = newCount;
	return this;
}
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcEnd > count) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        System.arraycopy(value, offset + srcBegin, dst, dstBegin,
             srcEnd - srcBegin);
    }



這是AbstractStringBuilder 中的append方法,實現原理很簡單,AbstractStringBuilder 內部數據結構是字符數組,append方法先判斷字符數組長度是否夠,如果不夠就創建一個更大的字符數組,將原有字符和新字符複製到新建數組中。複製使用的是System類中的arrayCopy方法。

StringBuffer中的append方法:

 public StringBuffer(String str) {
	super(str.length() + 16);
	append(str);
}

StringBuilder中的append方法:

public StringBuilder append(String str) {
	super.append(str);
        return this;
}

從這三個方法可以看出,StringBufferStringBuilder通過繼承,直接調用了父類中方法的實現,那這二者有什麼區別呢?

StringBufferStringBuilder最大的區別在於:StringBuffer是線程安全的,它的相關方法都加了鎖synchronizedStringBuilder是線程不安全的,在單線程環境中,使用StringBuilder比使用StringBuffer效率要高。

2 StringBufferString的效率問題

先上一段測試代碼:

class Test {
	public static void main(String args[]) {
		String s1 = "s1";
		String s2 = "s2";
		String s3 = "s3";
		String s4 = "s4";
		String s5 = "s1" + "s2" + "s3" + "s4";
		String s6 = s1 + s2 + s3 + s4;
		StringBuffer buf = new StringBuffer();
		buf.append(s1).append(s2).append(s3).append(s4);
	}
}

下面是使用DJ Java DCompiler3.7反編譯Text.class得到的結果:

class Test{
Test(){}
public static void main(String args[]){
 String s1 = "s1";
 String s2 = "s2";
 String s3 = "s3";
 String s4 = "s4";
 String s5 = "s1s2s3s4";//對應 String s5="s1"+"s2"+"s3"+"s4"
 String s6 = (new StringBuilder(String.valueOf(s1))).append(s2).append(s3).append(s4).toString();//對應String s6=s1+s2+s3+s4;
 StringBuffer buf = new StringBuffer();
 buf.append(s1).append(s2).append(s3).append(s4);
    }
}

從反編譯的代碼中可以看出:

String s5 = "s1" + "s2" + "s3" + "s4"

經編譯器優化後,變爲

String s5 = "s1s2s3s4";

故這二者是等價的

String s6 = s1 + s2 + s3 + s4;

反編譯之後得到的代碼爲:

String s6 = (new StringBuilder(String.valueOf(s1))).append(s2).append(s3).append(s4).toString();

已然很明確了,通過“+”進行字符串拼接實際上是調用了StringBuilderappend方法。

這樣這兩種拼接字符的方法效率就是相同的了。

String str =""; 
for(int i=0;i<100;i++){   
	str+=i;  
} 
 StringBuffer sb =new  StringBuffer();
   for(int i=0;i<100;i++){   
	sb.append(i);   
  } 

如果是這種情況,顯然是使用StringBuffer效率要高,前者每次使用字符串拼接都會創建一個StringBuilder對象,然後使用toString方法得到拼接後的字符串,顯然多做了一些工作。

關於String的特殊性介紹,請看博客String is special點擊打開鏈接

這裏補充一些內容:

1、String s = new String("abc");這句代碼究竟創建了幾個String對象

這段代碼調用的是這樣的構造方法:

public String(String original) {
   //other code …
}

所以,String s = new String("abc")可以分解爲兩步:

1 String original = "abc";

2 String s = new String(original );

這樣,顯然是創建了兩個對象。至於String original = "abc"這句代碼的原理,String is special中已經講得很清楚了。

2、public native String intern();

這是一個本地方法。在調用這個方法時,JAVA虛擬機首先檢查字符串池中是否已經存在與該對象值相等對象存在,如果有則返回字符串池中對象的引用;如果沒有,則先在字符串池中創建一個相同值的String對象,然後再將它的引用返回。

public class StringInternTest {
public static void main(String[] args) {
// 使用char數組來初始化a,避免在a被創建之前字符串池中已經存在了值爲”abcd”的對象
    String a = new String(new char[] { ‘a’, ‘b’, ‘c’, ‘d’ });
    String b = a.intern();
    if (b == a) {
        System.out.println(“b被加入了字符串池中,沒有新建對象”);
    } else {
        System.out.println(“b沒被加入字符串池中,新建了對象”);
    }
 }
}

運行結果:

b沒被加入字符串池中,新建了對象

如果String類的intern()方法在沒有找到相同值的對象時,是把當前對象加入字符串池中,然後返回它的引用的話,那麼ba指向的就是同一個對象;否則b指向的對象就是JAVA虛擬機在字符串池中新建的,只是它的值與a相同罷了。上面這段代碼的運行結果恰恰印證了這一點。

錯誤之處,請指正,共同學習,謝過!



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章