關於StringBuilder和StringBuffer你瞭解多少?

一、介紹

1、StringBuilder和StringBuffer的相同點

StringBuilder和StringBuffer都代表可變字符序列的類,上節講的String是不可變字符序列,StringBulder和StringBuffer都繼承了AbstractStringBuilder,除了序列化相關的其他操作都是調用父類AbstractStringBuilder的方法實現的,兩個類代碼基本上相同。

2、StringBuilder和StringBuffer的不同點

StriingBuilder不是線程安全的,用在單線程環境,而StringBuffer是線程安全的,可以用在多線程環境,StringBuffer的方法都添加了synchronized關鍵字,synchronized就是java中的內置鎖,多個線程對同一個StringBuffer操作時需要獲取鎖,StringBuffer爲了提升性能,toString方法使用了緩存,我們看以下代碼。

private transient char[] toStringCache;
    @Override
    public synchronized String toString() {
        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        return new String(toStringCache, true);
    }

從代碼中我們看出,如果toStringCache爲空,則調用Arrays.copyOfRange(value, 0, count)拷貝當前StringBuffer內部數組的數據並賦值給toStringCache,如果StringBuffer對象沒有改變,則下次調用就省去了拷貝數據的開銷,直接使用toStringCache。如果StringBuffer對象有修改操作就會將toStringCache置空,下次調用toString還要重新拷貝數據,讀取操作不會置空toStringCache。置空就是將toStringCache引用賦值nul,我們也看到了toString方法添加了synchronized關鍵字。

二、代碼講解

因爲StringBuilder和StringBuffer的大部分操作都是調用父類AbstractStringBuilder的方法,因此我們除了構造函數以外只講解AbstractStringBuilder的代碼。

StringBuilder構造函數(StringBuffer類似)

  public StringBuilder() {
        super(16);
    }

無參構造函數,調用父類構造函數,創建大小爲16的字符數組。

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

創建指定大小的字符數組

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

    public StringBuilder(CharSequence seq) {
        this(seq.length() + 16);
        append(seq);
    }

字符串或者字符序列爲參數的構造函數,先調用父類的構造函數創建字符串或者字符序列的長度加上16,爲什麼加上16呢,因爲StringBuilder和StringBuffer是可變字符序列,因此繼續添加字符串的可能性很大,因此原長度加上16可以避免內部char數組的擴容,前提添加的字符串小於16.

AbstractStringBuilder

1、屬性

char[] value;存儲數據的數組
int count; 當前數量大小

2、構造方法

    //子類序列化需要
    AbstractStringBuilder() { 
    }
    //創建指定大小的char數組
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }

 

3、常用方法

public void ensureCapacity(int minimumCapacity) 確保char數組容量至少等於minimumCapacity,如果當前char數組容量小於minimumCapacity,將進行擴容,擴容是新建一個當前char數組容量乘以2加2的新數組,並把當前char數組的數據拷貝到新建數組中去。如果minimumCapacity爲負數將不進行任何操作。
   public void ensureCapacity(int minimumCapacity) {
        if (minimumCapacity > 0) //1
            ensureCapacityInternal(minimumCapacity);
    }

    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) { //2
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }

    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2; //3
        if (newCapacity - minCapacity < 0) { //4
            newCapacity = minCapacity;
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0) //5
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }

   private int hugeCapacity(int minCapacity) {
        if (Integer.MAX_VALUE - minCapacity < 0) { // overflow  //6
            throw new OutOfMemoryError();
        }
        return (minCapacity > MAX_ARRAY_SIZE)  //7
            ? minCapacity : MAX_ARRAY_SIZE;
    }

1、指定的minimumCapacity如果小於0則直接返回

2、指定的minimumCapacity大於當前char數組容量才進行擴容,先調用newCapacity計算新容量,然後調用Arrays.copyOf拷貝一個指定大小的新數組並賦值給當前對象的char數組,新數組中包含原來數組的所有值。Arrays.copyOf使用了System.arraycopy方法,上節已經介紹過,不再贅述。

3、value.length左移一位,等於乘以2,然後加2,計算擴容的數組大小,爲什麼乘以2呢?因爲如果擴容後的數組過大會造成存儲空間的浪費,如果擴容後的數組過小會造成數組的頻繁擴容影響性能,因此爲了權衡選擇了兩倍擴容,兩倍擴容也是計算機領域常用方法。爲什麼後面還要加2呢?在value的大小爲0的情況下,如果不加2newCapacity的大小是0,會造成擴容失敗,當然有人會有疑問了,什麼情況下當前的char數組大小爲0呢,子類的構造函數不是創建了大小爲16的數組嗎?關鍵在trimToSize方法,如果當前對象的count爲0,調用此方法當前數組會縮減爲大小爲0的數組。

4、如果根據當前char數組的大小擴容後的newCapacity小於指定的minCapacity,則使用minCapacity當作擴容後的數組大小。

5、如果newCapacity小於0(溢出)或者newCapacity大於Integer.MAX_VALUE - 8,說明指數擴容後的大小或者指定的minCapacity都過大,則調用hugeCapacity計算容量,否則返回指數擴容後的容量。

6、指定的minCapacity大於Integer.MAX_VALUE,直接拋出OOM異常

7、minCapacity大於Integer.MAX_VALUE - 8則返回minCapacity,否則返回Integer.MAX_VALUE - 8

public AbstractStringBuilder append(String str) 添加指定的字符串到當前字符序列
    public AbstractStringBuilder append(String str) {
        if (str == null)  //1
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len); //3
        str.getChars(0, len, value, count); //4
        count += len;
        return this;
    }

    private AbstractStringBuilder appendNull() { //2
        int c = count;
        ensureCapacityInternal(c + 4);
        final char[] value = this.value;
        value[c++] = 'n';
        value[c++] = 'u';
        value[c++] = 'l';
        value[c++] = 'l';
        count = c;
        return this;
    }

其他append重載方法實現大都類似,不再講解。

1、如果指定字符串爲null,則調用appendNull將‘null’四個字符串添加到char數組中,而不是什麼都沒有添加。

2、appendNull方法先調用ensureCapacityInternal確保當前數組容量是否能夠存儲count+4大小的數據,然後當前char數組添加null四個字符,更新count後返回。

3、調用ensureCapacityInternal確保容量足夠添加指定字符串

4、調用str.getChars(0, len, value, count)將str的數據拷貝到value從count的開頭處,getChars方法內部也是調用System.arraycopy,更新count後返回。

public AbstractStringBuilder insert(int offset, String str) 指定位置處插入字符串
    public AbstractStringBuilder insert(int offset, String str) {
        if ((offset < 0) || (offset > length()))  //1
            throw new StringIndexOutOfBoundsException(offset);
        if (str == null) //2
            str = "null";
        int len = str.length();
        ensureCapacityInternal(count + len); //3
        System.arraycopy(value, offset, value, offset + len, count - offset); //4
        str.getChars(value, offset); //5
        count += len;
        return this;
    }

1、確保offset參數是否合法,非法則拋出索引越界異常。

2、如果指定字符串爲null,則將字符串指定“null”字符串,字符序列會添加"null"字符串。

3、確保容量大小是否足夠。

4、 調用System.arraycopy(value, offset, value, offset + len, count - offset)將value中索引爲offset以後的字符序列拷貝到offset + len處,len爲指定字符串的長度,offset到offset + len處的索引存儲指定的字符串str

5、調用str.getChars(value, offset)將指定字符串str拷貝到value的索引offset處,value中的索引offset到offset + len存儲了指定字符串str.,更新count後返回。

AbstractStringBuilder delete(int start, int end) 刪除索引start到end的字符序列
    public AbstractStringBuilder delete(int start, int end) {
        if (start < 0)  //1
            throw new StringIndexOutOfBoundsException(start);
        if (end > count)
            end = count;
        if (start > end)
            throw new StringIndexOutOfBoundsException();
        int len = end - start;
        if (len > 0) {  //2
            System.arraycopy(value, start+len, value, start, count-end);
            count -= len;
        }
        return this;

1、判斷start和end是否合法

2、將val中start+len索引後的字符序列拷貝到start索引處,start索引到start+len索引的字符序列被start+len索引後的字符序列覆蓋,相當與刪除,更新count後返回。注意,System.arraycopy只是拷貝數組中的數據,原來位置的數據仍然沒有變,只不過toString的時候只獲取char數組中count範圍內的字符序列。

public AbstractStringBuilder reverse() 字符序列翻轉
 public AbstractStringBuilder reverse() {
        boolean hasSurrogates = false;
        int n = count - 1;
        for (int j = (n-1) >> 1; j >= 0; j--) { //1
            int k = n - j;
            char cj = value[j];
            char ck = value[k];
            value[j] = ck;
            value[k] = cj;
            if (Character.isSurrogate(cj) ||
                Character.isSurrogate(ck)) {
                hasSurrogates = true;
            }
        }
        if (hasSurrogates) { //2
            reverseAllValidSurrogatePairs();
        }
        return this;
    }

    /** Outlined helper method for reverse() */
    private void reverseAllValidSurrogatePairs() {
        for (int i = 0; i < count - 1; i++) { //3
            char c2 = value[i];
            if (Character.isLowSurrogate(c2)) {
                char c1 = value[i + 1];
                if (Character.isHighSurrogate(c1)) {
                    value[i++] = c1;
                    value[i] = c2;
                }
            }
        }
    }

1、遍歷char數組,計算j和k,j相當於中位數往左第一個字符,k相當於中位數往右一個字符,依次遞減j,遞增k,交換j處和k處的字符,完成char數組中的字符的翻轉,hasSurrogates 記錄字符數組中是否存在增補字符的標識,增補字符是由兩個char組成,第一個char是高代理項,第二個char是低代理項,大家自行查詢資料。

2、hasSurrogates爲true,char數組中存在增補字符,調用reverseAllValidSurrogatePairs處理增補字符

3、遍歷char數組,如果當前索引下存在增補字符,說明這個char是翻轉後的增補字符,我們舉個例子,例如1234字符串翻轉後爲4321,如果12是增補字符,翻轉不應該改變增補字符的順序,則翻轉後應該是4312,因此我們要改變翻轉後增補字符兩個char的順序,翻轉前增補字符是高代理項在前,低代理項在後,翻轉後低代理項在前,高代理項在後,因此代碼中isLowSurrogate判斷索引下的char是否是低代理項,是的話與下一個高代理項char交換順序。

其他方法大都類似或者調用的String的方法實現,不再講解,下篇文章給大家講解Integer類。

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