1 StringBuffer與StringBuilder
StringBuffer與StringBuilder都是final類,不能被繼承。
上圖是二者的UML圖,可以看出,它們都繼承了抽象類AbstractBuilder。AbstractBuilder定義了StringBuffer與StringBuilder的基本操作。
這是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;
}
從這三個方法可以看出,StringBuffer和StringBuilder通過繼承,直接調用了父類中方法的實現,那這二者有什麼區別呢?
StringBuffer和StringBuilder最大的區別在於:StringBuffer是線程安全的,它的相關方法都加了鎖synchronized,StringBuilder是線程不安全的,在單線程環境中,使用StringBuilder比使用StringBuffer效率要高。
2 StringBuffer與String的效率問題
先上一段測試代碼:
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);
}
}
從反編譯的代碼中可以看出:
1 String s5 = "s1" + "s2" + "s3" + "s4"
經編譯器優化後,變爲
String s5 = "s1s2s3s4";
故這二者是等價的
2 String s6 = s1 + s2 + s3 + s4;
反編譯之後得到的代碼爲:
String s6 = (new StringBuilder(String.valueOf(s1))).append(s2).append(s3).append(s4).toString();
已然很明確了,通過“+”進行字符串拼接實際上是調用了StringBuilder的append方法。
這樣這兩種拼接字符的方法效率就是相同的了。
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()方法在沒有找到相同值的對象時,是把當前對象加入字符串池中,然後返回它的引用的話,那麼b和a指向的就是同一個對象;否則b指向的對象就是JAVA虛擬機在字符串池中新建的,只是它的值與a相同罷了。上面這段代碼的運行結果恰恰印證了這一點。
錯誤之處,請指正,共同學習,謝過!