深入理解Java常用類-----StringBuilder

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

  • 強大的父類AbstractStringBuilder
  • 多重載的構造函數
  • 重要的append方法
  • 其他一些方法的簡單介紹

一、強大的父類AbstractStringBuilder
?????StringBuilder的大部分方法中都會調用父類方法或屬性, 足以見得該父類對其的影響還是很大的,所以我們將從頭至尾簡單介紹下它的父類AbstractStringBuilder。該類中只有兩個屬性:

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

value屬性表示的是一個字符數組,該數組的作用和String中的字符數組的作用是一樣的,只是此value數組并沒有被final修飾,也就是說該數組內部的值是可以動態修改的,這也是StringBuilder存在的意義。count屬性表示的不是value數組的長度,它代表的是value數組中實際上存放的字符數目,例如:value長度為10,我存放8個字符,剩下位置為空,此時count的值就為8,而value.length()為10。

兩個構造方法都不是public,他們都是被設計出來給子類使用的。

AbstractStringBuilder() {}

//初始化value
AbstractStringBuilder(int capacity) {
     value = new char[capacity];
}

還有兩個用于返回長度的方法:

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

一個返回的是實際存放的字符數目,另一個返回的是內置字符數組的長度。
還有一個用于保證字符數組長度的方法,該方法和我們之前介紹的ArrayList中用于動態擴容的方法具有一樣的功效。

void expandCapacity(int minimumCapacity) {
    int newCapacity = value.length * 2 + 2;
    if (newCapacity - minimumCapacity < 0)
        newCapacity = minimumCapacity;
    if (newCapacity < 0) {
        if (minimumCapacity < 0) // overflow
            throw new OutOfMemoryError();
        newCapacity = Integer.MAX_VALUE;
    }
    value = Arrays.copyOf(value, newCapacity);
}

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

這種動態擴容的思想還被用于ArrayList中,成為它和普通數組的核心優勢。這種思想其實是一種折中的解決方案,它既避免了一次性創建很大的數組所導致的資源浪費,也解決了那種一旦創建就不能更改的靜態局限性。這種思想值得我們學習和應用。

該類還提供一種方法,去除value數組中所有為空的元素:

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

看個例子:

public static void main(String[] args){
    StringBuilder sb = new StringBuilder();
    sb.append("hello");
    System.out.println(sb.length());
    System.out.println(sb.capacity());

    sb.trimToSize();
    System.out.println(sb.length());
    System.out.println(sb.capacity());
}

我們看輸出結果:

5
16
5
5

需要解釋下,沒有顯式指定value的長度,則會默認為16,程序為value的前5個位置賦值,后面的位置為空,所以我們看到第一次輸出結果是有區別的。但是我們的trimToSize方法把沒有使用的空位置全部清除,第二次輸出結果顯示capcity和length是一樣的。

該類中其他的一些方法,例如:getChars,charAt等,這些方法都是很String類中對應的方法具有一樣功效。下面我們主要看看一個重要的方法,append。該方法也具有相當多的重載,所以我們慢慢看。

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

append這個方法是我們使用StringBuilder時最常用到的一個方法,該方法用于追加一個字符串到原StringBuilder對象的尾部。該方法接過來一個String對象,如果為null將會調用appendNull方法把字符串“null”追加到原對象的末尾,否則將會把該字符串追加到原對象的末尾。

其他重載都是以各種各樣的形式添加字符串到原StringBuilder對象的末尾,如果傳入的是int,long,double,boolean等類型的參數,那么程序會將他們轉換為字符串類型添加到末尾。我們也可以指定添加一個字符串的一部分,例如:

public AbstractStringBuilder append(char str[], int offset, int len)

至此,我們就已經完成AbstractStringBuilder這個超類的簡單介紹,至于其中還有一些其他方法,我們將從他的實現類StringBuilder中繼續介紹。下面我們看看有關StringBuilder類的一些常用方法的介紹。

二、多重載的構造函數
?????StringBuilder除了封裝了一個版本號,并沒有封裝任何其他的屬性,甚至沒有封裝字符數組,那是因為它高度依賴他的父類,使用的是父類中封裝的字符數組。包括他的構造函數也是調用的父類中的構造函數,例如:

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

這些構造函數會調用父類的一個構造函數為value字符數組初始化長度,如果沒有顯式傳入需要設定的數組長度,則會默認為16,這一點我們在之前的實例中曾演示過。這些構造器主要完成的工作就是初始化一個字符數組。下面我們看StringBuilder的一個重要方法:append。

三、重要的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;
}

其實別看這么多重載,實際上都是相互調用,真正有用的就一兩個。他們這些方法重載內部都會調用父類中相對應的方法,雖然沒有接受返回值,但是父類方法完成了對value數組的賦值操作,最后調用完成append方法之后返回的是StringBuilder對象,這意味著我們可以連續調用append方法。

其實我們需要始終明白一點,StringBuilder和StringBuffer他們其實和String差不多,內部一樣都是封裝的字符數組,只不過StringBuilder實現了動態擴容機制,可以動態擴容并且可以動態更改value數組中的元素而已,但本質上都是一樣的。

四、有關StringBuilder的一些其他使用細節
?????首先我們看一個刪除的方法,該方法可以指定刪除StringBuilder對象中指范圍內的子串。

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

該方法中最核心的方法是System.arraycopy,這個方法也被廣泛使用在數組拷貝轉移中。該方法將value數組索引位置為start+len開始以后的所有字符向前移動到start索引位置起,start+count-len終止位置處。需要記住的是,該方法并沒有創建一個新數組,而是對原value數組進行移動元素來實現的。實際上該方法就是將即將被刪除的子串后面的所有字符整體移動到被刪除子串的開始位置。

這里寫圖片描述

value數組長度沒有發生改變,只是用后面的子串覆蓋了將要被刪除的子串,然后count -= len;更新count,但是實際上并沒有刪除。但是count的值指定了該value數組中有效的字符數目,雖然沒有具體刪除該元素,但是在輸出的時候只會把前count個字符作為有效字符輸出。這就是delete的底層操作,包括其中的deleteCharAt刪除指定位置的字符,原理都是一樣的。

該類中還有一些replace,substring等方法,這些方法和我們之前曾介紹過的String類中相對應的方法底層實現都是類似,此處不再贅述了。下面我們看一個String類中沒有的方法,insert。

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

有了之前delete方法學習的基礎之后,這個插入的方法就很簡單了。該方法接受四個參數,第一個參數表示要插入的索引位置,第二個參數表示要插入的字符數組或者字符串,第三個參數和第四個參數用于截取該原字符數組。核心方法依然是System.arraycopy,不過這里調用了兩次,第一次的調用將index位置之后的所有字符往后移動len個長度(為即將插入的字符串留下空位置),第二次調用將該字符數組插入到預留位置。

該insert方法有很多重載,但是本質上都離不開我們上述介紹的這個方法。所以為了借閱篇幅,此處不再贅述。

至此,我們就簡單介紹完成了StringBuilder的基本使用,理解不到之處,望大家指出,相互學習!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容