Java中的String,StringBuffer,StringBuilder區別

String(大姐,出生於JDK1.0時代) 不可變字符序列
StringBuffer(二姐,出生於JDK1.0時代) 線程安全的可變字符序列
StringBuilder(小妹,出生於JDK1.5時代) 非線程安全的可變字符序列

String源碼:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {

    private final char value[];

}

StringBuilder源碼:

public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence
{

    public StringBuilder() {
        super(16);
    }

    public StringBuilder(int capacity) {
        super(capacity);
    }

    public StringBuilder(String str) {
        super(str.length() + 16);
        append(str);
    }
    @Override
    public StringBuilder append(Object obj) {
        return append(String.valueOf(obj));
    }
    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

    public StringBuilder append(StringBuffer sb) {
        super.append(sb);
        return this;
    }

StringBuffer源碼:

public final class StringBuffer extends AbstractStringBuilder  implements java.io.Serializable, CharSequence
{
    private transient char[] toStringCache;

    public StringBuffer() {
        super(16);
    }

    public StringBuffer(int capacity) {
        super(capacity);
    }

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

  @Override
    public synchronized void setCharAt(int index, char ch) {
        if ((index < 0) || (index >= count))
            throw new StringIndexOutOfBoundsException(index);
        toStringCache = null;
        value[index] = ch;
    }
    @Override
    public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }
    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
}

(1) String中的是常量(final)數組,只能被賦值一次。
比如:new String(“abc”)使得value[]={‘a’,’b’,’c’}(查看jdk String 就是這麼實現的),之後這個String對象中的value[]再也不能改變了。這也正是大家常說的,String是不可變的原因 。
注意:這個對初學者來說有個誤區,有人說String str1=new String(“abc”); str1=new String(“cba”);不是改變了字符串str1嗎?那麼你有必要先搞懂對象引用和對象本身的區別。這裏我簡單的說明一下,對象本身指的是存放在堆空間中的該對象的實例數據(非靜態非常量字段)。而對象引用指的是堆中對象本身所存放的地址,一般方法區和Java棧中存儲的都是對象引用,而非對象本身的數據。

(2) StringBuffer中的value[]就是一個很普通的數組,而且可以通過append()方法將新字符串加入value[]末尾。這樣也就改變了value[]的內容和大小了。
比如:new StringBuffer(“abc”)使得value[]={‘a’,’b’,’c’,”,”…}(注意構造的長度是str.length()+16)。如果再將這個對象append(“abc”),那麼這個對象中的value[]={‘a’,’b’,’c’,’a’,’b’,’c’,”….}。這也就是爲什麼大家說 StringBuffer是可變字符串 的涵義了。從這一點也可以看出,StringBuffer中的value[]完全可以作爲字符串的緩衝區功能。其累加性能是很不錯的,在後面我們會進行比較。

總結,討論String和StringBuffer可不可變。本質上是指對象中的value[]字符數組可不可變,而不是對象引用可不可變。

(3)StringBuffer與StringBuilder的線程安全性問題
StringBuffer和StringBuilder可以算是雙胞胎了,這兩者的方法沒有很大區別。但在線程安全性方面,StringBuffer允許多線程進行字符操作。這是因爲在源代碼中StringBuffer的很多方法都被關鍵字synchronized 修飾了,而StringBuilder沒有。

注意:是不是String也不安全呢?事實上不存在這個問題,String是不可變的。線程對於堆中指定的一個String對象只能讀取,無法修改。

★String和StringBuffer的效率問題(這可是個熱門話題呀!)
首先說明一點:StringBuffer和StringBuilder可謂雙胞胎,StringBuilder是1.5新引入的,其前身就是StringBuffer。StringBuilder的效率比StringBuffer稍高,如果不考慮線程安全,StringBuilder應該是首選。另外,JVM運行程序主要的時間耗費是在創建對象和回收對象上。

(1) String常量與String變量的”+”操作比較
原因:測試①的”Heart”+”Raid”在編譯階段就已經連接起來,形成了一個字符串常量”HeartRaid”,並指向堆中的拘留字符串對象。運行時只需要將”HeartRaid”指向的拘留字符串對象地址取出1W次,存放在局部變量str中。這確實不需要什麼時間。
測試②中局部變量s1和s2存放的是兩個不同的拘留字符串對象的地址。然後會通過下面三個步驟完成“+連接”:
1、StringBuilder temp=new StringBuilder(s1),
2、temp.append(s2);
3、str=temp.toString();
我們發現,雖然在中間的時候也用到了append()方法,但是在開始和結束的時候分別創建了StringBuilder和String對象。可想而知:調用1W次,是不是就創建了1W次這兩種對象呢?不划算。

(2)String對象的”累+”連接操作與StringBuffer對象的append()累和連接操作比較。
結論:大量字符串累加時,StringBuffer的append()效率遠好於String對象的”累+”連接
原因:測試① 中的s=s+s1,JVM會利用首先創建一個StringBuilder,並利用append方法完成s和s1所指向的字符串對象值的合併操作,接着調用StringBuilder的 toString()方法在堆中創建一個新的String對象,其值爲剛纔字符串的合併結果。而局部變量s指向了新創建的String對象。
因爲String對象中的value[]是不能改變的,每一次合併後字符串值都需要創建一個新的String對象來存放。循環1W次自然需要創建1W個String對象和1W個StringBuilder對象,效率低就可想而知了。

   測試②中sb.append(s1);只需要將自己的value[]數組不停的擴大來存放s1即可。循環過程中無需在堆中創建任何新的對象。效率高就不足爲奇了。 

總結:
(1) 在編譯階段就能夠確定的字符串常量,完全沒有必要創建String或StringBuffer對象。直接使用字符串常量的”+”連接操作效率最高。
(2) StringBuffer對象的append效率要高於String對象的”+”連接操作。
(3) 不停的創建對象是程序低效的一個重要原因。那麼相同的字符串值能否在堆中只創建一個String對象那。顯然拘留字符串能夠做到這一點,除了程序中的字符串常量會被JVM自動創建拘留字符串之外,調用String的intern()方法也能做到這一點。當調用intern()時,如果常量池中已經有了當前String的值,那麼返回這個常量指向拘留對象的地址。如果沒有,則將String值加入常量池中,並創建一個新的拘留字符串對象。

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