JDK源碼學習--StringBuilder類

上文我們介紹過JDK源碼學習–String類,該類的內部用了一個char數組表示一個字符串對象的,只是該字符數組被final修飾,初始化之後就不能被修改,但是對於經常做字符串修改操作的情況下,String類就需要不斷創建新對象,性能極低。StringBuilder內部也是封裝的一個字符數組,只不過該數組非final修飾,可以不斷修改。所以對於一些經常需要修改字符串的情況,我們應當首選StringBuilder。StringBuilder和StringBuffer內部代碼幾乎一樣,StringBuffer的所有方法都被關鍵字synchronized修飾,它是線程安全的,但是線程安全是需要付出性能代價的,在實際使用中,適情況選擇。


一、實現接口

StringBuilder的大部分方法中都會調用父類AbstractStringBuilder方法或屬性,實現了Serializable和CharSequence接口。

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

在講StringBuilder之前先簡單介紹下它的父類AbstractStringBuilder,實現了Appendable和CharSequence接口

abstract class AbstractStringBuilder implements Appendable, CharSequence 

1、父類AbstractStringBuilder成員變量

該類有兩個成員變量,value數組和String類中的char數組是一樣的,只不過沒有被final修飾,該數組內部的值是可以動態修改的,這也是StringBuilder存在的意義。count表示的是value數組中實際上存放的字符數目,例如:value長度爲10,我存放8個字符,剩下位置爲空,此時count的值就爲8,而value.length()爲10。

    //The value is used for character storage.
    char[] value;
    //The count is the number of characters used.
    int count;

2、父類AbstractStringBuilder構造函數

有參構造方法初始化容量,兩個構造方法都不是public,被設計出來給子類使用的

    //This no-arg constructor is necessary for serialization of subclasses.
    AbstractStringBuilder() {
    }
    //Creates an AbstractStringBuilder of the specified capacity
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }

3、父類AbstractStringBuilder常用方法

length()方法返回的是實際存放的字符數目,capacity()方法返回的是內置字符數組的長度。

public int length() {
    return count;
}
public int capacity() {
    return value.length;
}

ensureCapacity(int minimumCapacity)

保證字符數組長度的方法,對於一個StringBuilder對象,可以不斷的添加字符串到其中,這樣就會遇到value數組長度不夠的時候,該方法就是用於處理這種情況。在我們實際操作value數組之前,大多會調用該方法判斷此次操作之後是否會導致數組溢出,如果是則會將原數組長度擴大兩倍加上2並拷貝原數組中的內容到新數組中,然後才實際操作value數組。

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

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

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

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

trimToSize()

去除value數組中所有爲空的元素

public void trimToSize() {
   if (count < value.length) {
       value = Arrays.copyOf(value, count);
   }
}

append方法集

append這個方法是我們使用StringBuilder時最常用到的一個方法,該方法用於追加一個字符串到原StringBuilder對象的尾部。該方法接過來一個String對象,如果爲null將會調用appendNull方法把字符串“null”追加到原對象的末尾,否則將會把該字符串追加到原對象的末尾。其他重載都是以各種各樣的形式添加字符串到原StringBuilder對象的末尾,如果傳入的是int,long,double,boolean等類型的參數,那麼程序會將他們轉換爲字符串類型添加到末尾

 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;
    }

    // Documentation in subclasses because of synchro difference
    public AbstractStringBuilder append(StringBuffer sb) {
        if (sb == null)
            return appendNull();
        int len = sb.length();
        ensureCapacityInternal(count + len);
        sb.getChars(0, len, value, count);
        count += len;
        return this;
    }

    /**
     * @since 1.8
     */
    AbstractStringBuilder append(AbstractStringBuilder asb) {
        if (asb == null)
            return appendNull();
        int len = asb.length();
        ensureCapacityInternal(count + len);
        asb.getChars(0, len, value, count);
        count += len;
        return this;
    }

    // Documentation in subclasses because of synchro difference
    @Override
    public AbstractStringBuilder append(CharSequence s) {
        if (s == null)
            return appendNull();
        if (s instanceof String)
            return this.append((String)s);
        if (s instanceof AbstractStringBuilder)
            return this.append((AbstractStringBuilder)s);

        return this.append(s, 0, s.length());
    }

    private AbstractStringBuilder appendNull() {
        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;
    }

二、StringBuilder構造方法:

StringBuilder因爲它高度依賴他的父類,沒有封裝任何其他的屬性,甚至沒有封裝字符數組,使用的是父類中封裝的字符數組,包括他的構造函數也是調用的父類中的構造函數。這些構造函數會調用父類的一個構造函數爲value字符數組初始化長度,如果沒有顯式傳入需要設定的數組長度,則會默認爲16。

	public StringBuilder() {
        super(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);
    }

三、StringBuilder常用方法

1、writeObject、readObject方法

writeObject(java.io.ObjectOutputStream s)在進行序列化的時候保存StringBuilder對象的狀態到一個流中。
readObject(java.io.ObjectInputStream s)反序列化時從流中獲取StringBuild對象序列化之前的狀態。

 	private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        s.defaultWriteObject();
        s.writeInt(count);
        s.writeObject(value);
    }

    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        count = s.readInt();
        value = (char[]) s.readObject();
    }

2、toString()

此方法重寫了父類的toString()方法,創建了一個新的字符串對象,創建一個副本,而不要去共享這個內部維護的數組,因爲返回的是String對象,不可變的,如果返回了數組的共享,在改變StringBuilder對象時,String對象的內容隨之改變,這就破壞了String對象的不可變性。

	@Override
    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }

3、reverse()

此方法重寫了父類的reverse(),調用父類的方法實現反轉此字符串

	@Override
    public StringBuilder reverse() {
        super.reverse();
        return this;
    }

4、delete(int start, int end)

該方法重寫了父類的delete方法,調用父類方法指定刪除StringBuilder對象中指範圍內的子串。該類中還有deleteCharAt(int index),根據角標刪除字符,不再一一贅述。

	@Override
    public StringBuilder delete(int start, int end) {
        super.delete(start, end);
        return this;
    }
	//父類中的delete方法
 	public AbstractStringBuilder delete(int start, int end) {
        if (start < 0)
            throw new StringIndexOutOfBoundsException(start);
        if (end > count)
            end = count;
        if (start > end)
            throw new StringIndexOutOfBoundsException();
        int len = end - start;
        if (len > 0) {
            System.arraycopy(value, start+len, value, start, count-end);
            count -= len;
        }
        return this;
    }

5、insert(int index, char[] str, int offset, int len)

該方法接受四個參數,第一個參數表示要插入的索引位置,第二個參數表示要插入的字符數組或者字符串,第三個參數和第四個參數用於截取該原字符數組。核心方法依然是System.arraycopy,不過這裏調用了兩次,第一次的調用將index位置之後的所有字符往後移動len個長度(爲即將插入的字符串留下空位置),第二次調用將該字符數組插入到預留位置,insert方法有很多重載,但是本質上大致相同,此處不再贅述。

	@Override
    public StringBuilder insert(int index, char[] str, int offset, int len)
    {
        super.insert(index, str, offset, len);
        return this;
    }

	//父類的方法
	public AbstractStringBuilder insert(int index, char[] str, int offset,
                                        int len)
    {
        if ((index < 0) || (index > length()))
            throw new StringIndexOutOfBoundsException(index);
        if ((offset < 0) || (len < 0) || (offset > str.length - len))
            throw new StringIndexOutOfBoundsException(
                "offset " + offset + ", len " + len + ", str.length "
                + str.length);
        ensureCapacityInternal(count + len);
        System.arraycopy(value, index, value, index + len, count - index);
        System.arraycopy(str, offset, value, index, len);
        count += len;
        return this;
    }

6、append方法

StringBuilder中的append方法都是重寫了父類中的append方法:

@Override
public StringBuilder append(boolean b) {
    super.append(b);
    return this;
}

@Override
public StringBuilder append(char c) {
    super.append(c);
    return this;
}

@Override
public StringBuilder append(int i) {
    super.append(i);
    return this;
}

@Override
public StringBuilder append(long lng) {
    super.append(lng);
    return this;
}

@Override
public StringBuilder append(float f) {
    super.append(f);
    return this;
}

四、總結

1、String是Java中基礎且重要的類,由於不可變性,在拼接字符串時候會產生很多無用的中間對象,如果頻繁的進行這樣的操作對性能有所影響。使用場景:在字符串不經常發生變化的業務場景優先使用String(代碼更清晰簡潔)。如常量的聲明,少量的字符串操作(拼接,刪除等)。

2、StringBuffer就是爲了解決大量拼接字符串時產生很多中間對象問題,所有修改數據的方法都加上了synchronized,保證了線程安全,但是保證了線程安全是需要性能的代價的。使用場景:在多線程情況下,如有大量的字符串操作情況,應該使用StringBuffer。如HTTP參數解析和封裝等。

3、StringBuilder解決了大量拼接字符串時產生很多中間對象問題,並且字符串拼接操作不需要線程安全。StringBuilder和StringBuffer他們其實和String差不多,內部一樣都是封裝的字符數組,只不過StringBuilder實現了動態擴容機制,可以動態擴容並且可以動態更改value數組中的元素而已,但本質上都是一樣的。在單線程情況下,如有大量的字符串操作情況,應該使用StringBuilder來操作字符串。使用場景:不能使用String"+"來拼接而是使用,避免產生大量無用的中間對象,耗費空間且執行效率低下(新建對象、回收對象花費大量時間)。如JSON的封裝等。

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