【資料結構】串(String、StringBuilder、StringBuffer)的JAVA程式碼實現

Yngz_Miao發表於2018-03-24

串即字串,是由0或多個字元組成的有限序列,是資料元素為單個字元的特殊線性表。

串從資料結構上來說是一種特殊的線性表,其特殊性在於串中的資料元素是一個個的字元。但是,串的基本操作和線性表的基本操作相比卻有很大的不同,線性表的操作主要是針對線性表的某個資料元素進行的,而串上的操作主要是針對串的整體或串的某一部分子串進行的。

本文主要介紹Java中的字串類String、StringBuilder、StringBuffer。

 

串的抽象資料型別

  1. 資料元素:只能是字元型別;
  2. 資料結構:資料元素之間呈線性關係;
  3. 資料操作:對串的基本操作定義在IString中,程式碼如下:
public interface IString {
	int length();//求串長度
    IString concat(IString str);//串的連線
    int compareTo(IString anotherString);//串的比較
    IString substring(int start, int end);//求子串
	int indexOf(IString str, int fromIndex);//串定位
	IString append(String str);//串附加
	IString delete(int start, int end);//串刪除
	IString insert(int offset, IString str);//串插入	
}

 

Java中的字串類String

先看一下String類的宣告

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

String類的儲存結構

String類採用順序儲存結構,使用字元陣列儲存字串,字元陣列的長度與字串中字元的個數相同。String類中的儲存的字串陣列空間一旦建立,內容就無法改變了。String類原始碼中儲存字串內容的陣列value為final的,程式碼如下:

private final char value[];

Java在建立字串時,使用不同的建立方式,表示字串的陣列也會在不同的記憶體區域建立。這裡,需要理解有關記憶體分配的三個術語:

  • 棧:由Java虛擬機器分配的用於儲存執行緒執行的動作和資料引用的記憶體區域。棧是一個執行的單位,Java中一個執行緒就會有一個與之對應的棧。這裡只會存放表示字串的陣列的引用。
  • 堆:由Java虛擬機器分配的用於儲存物件的記憶體區域。通過顯示呼叫建構函式的方式來建立字串物件,此時表示字串的陣列會存放在這個區域。
  • 常量池:在編譯的階段,在堆中分配出來的一塊儲存區域,用於存放基本型別常量和顯示宣告的字串的物件。

總之:在Java中,字串物件的引用是儲存在棧中的;如果是編譯期已經建立好,即指用雙引號定義的字串的儲存在常量池中;如果是執行期出來的物件,則儲存在堆中。對於通過equals方法比較相等的字串,在常量池中是隻有一份的,在堆中則是有多份。

String類的建構函式

在Java中建立String字串,是通過呼叫String字串類的建構函式來實現的。String常用的建構函式有下面三個:

    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }

建立一個新的字串,使其表示字元陣列引數中當前包含的字元序列。函式實現中的Arrays.copyOf(value, value.length)完成字元陣列的複製,並使得複製的陣列具有value.length的長度,實現新陣列與引數陣列內容相同。

在實際的開發中,很少有直接使用該建構函式建立字串物件,而使用類似String str="abc"比較多,檢視String類的原始碼文件,對其解釋為:

String str="abc";

等價於

char data[]={'a','b','c'};

String str=new String(data);

相當於在編譯時,自動呼叫String(char value[])建構函式在常量池中建立了常量字串物件"abc"。但由於並沒有顯示呼叫建構函式,在堆中並沒有存放,只有常量池中獨有。

    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

初始化一個新建立的String物件,使其表示一個與引數相同字元序列。在String str=new String("abc")中,就是使用了該建構函式。相當於在編譯時,Java虛擬機器會先去常量池中查詢是否有"abc"物件,如果沒有則在常量池中建立一個,然後再堆中也建立一個"abc"物件,並將str指向它;如果常量池中有則直接去堆中建立。

    public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count < 0) {
            throw new StringIndexOutOfBoundsException(count);
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }

建立一個新的String,它包含取自字元陣列引數一個子陣列的字元。offset引數指定子陣列第一個字元的索引,count表示長度。函式實現中的Arrays.copyOfRange(value, offset, offset+count)完成子字元陣列的複製。

String類的基本操作

    public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        System.arraycopy(str.value, 0, buf, len, str.value.length);
        return new String(buf, true);
    }

串連線函式concat(),Array.copyOf(value,len+otherLen)建立一個長度為兩個字串長度之和的新陣列buf,並將value複製到新陣列的開頭,剩餘空間用null字元填充。

    public static char[] copyOf(char[] original, int newLength) {
        char[] copy = new char[newLength];
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

其實,Array.copyOf()的內部實現也是通過System.arraycopy(src,srcPos,dest,destPos,length)函式實現的。這個函式引數的意義就是從src的srcPos位置複製長度length到dest的destPos位置。

    public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;
            }
            k++;
        }
        return len1 - len2;
    }

串比較函式compareTo(),基於字串中各個字元的Unicode值進行比較,通過Math.min來計算出字串中較短的長度

    public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }

求子串函式subString(),首先確認index的合法性,再通過呼叫建構函式String(char[],offset,count)來完成

    public int indexOf(String str, int fromIndex) {
        return indexOf(value, 0, value.length,
                str.value, 0, str.value.length, fromIndex);
    }
    static int indexOf(char[] source, int sourceOffset, int sourceCount,
            char[] target, int targetOffset, int targetCount,
            int fromIndex) {
        if (fromIndex >= sourceCount) {
            return (targetCount == 0 ? sourceCount : -1);
        }
        if (fromIndex < 0) {
            fromIndex = 0;
        }
        if (targetCount == 0) {
            return fromIndex;
        }

        char first = target[targetOffset];
        int max = sourceOffset + (sourceCount - targetCount);

        for (int i = sourceOffset + fromIndex; i <= max; i++) {
            /* Look for first character. */
            if (source[i] != first) {
                while (++i <= max && source[i] != first);
            }

            /* Found first character, now look at the rest of v2 */
            if (i <= max) {
                int j = i + 1;
                int end = j + targetCount - 1;
                for (int k = targetOffset + 1; j < end && source[j]
                        == target[k]; j++, k++);

                if (j == end) {
                    /* Found whole string. */
                    return i - sourceOffset;
                }
            }
        }
        return -1;
    }

串定位函式indexOf(),從指定的索引處開始返回指定引數字串在此字串中第一次出現處的索引,串定位也稱為模式匹配。首先確認index的合法性,然後根據首字元來尋找地址接上後續字元的匹配。

 

Java中的字串類StringBuilder和StringBuffer

由於String字串是常量字串,不方便進行插入和刪除操作,在對字串進行插入和刪除操作時,通常使用StringBuilder和StringBuffer類。

StringBuilder和StringBuffer類都繼承自AbstractStringBuilder類,有相同的屬性和方法,可以向其中插入或刪除字元,它們是可變字串。兩個類主要的區別是StringBuffer對方法加了同步鎖或對呼叫的方法加了同步鎖,是執行緒安全的,而StringBuilder是非執行緒安全的。下面以StringBuilder為例看可變字串類。

StringBuilder類的儲存結構

先看一下StringBuilder類的宣告

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

StringBuilder和String類一樣使用字元陣列儲存字串,但陣列的內容是可變的,儲存在StringBuilder字串的字元陣列定義在AbstractStringBuilder中,程式碼如下:

    char[] value;

StringBuilder類的建構函式

在Java中建立StringBuilder字串,是通過呼叫StringBuilder字串類的建構函式實現的。StringBuilder常用的建構函式有三個:

    public StringBuilder() {
        super(16);
    }

StringBuilder()呼叫父類侍衛建構函式建立一個不帶任何字元的字串生成器,其初始容量為16個字元。在使用的過程中,如果超過這個值,就會重新分配記憶體,建立一個更大的陣列,並將原來的陣列複製,再丟棄舊的陣列。下面是擴大陣列容量的方法:

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

我們可以看到,陣列的最大容量是Integer.MAX_VALUE

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

StringBuilder(capacity)構造一個不帶任何字元的字串生成器,其初始容量由capacity引數指定。

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

StringBuilder(str)構造一個不帶字串生成器,並初始化為指定字串內容,其初始容量是16加上字串引數的長度

StringBuilder類的基本操作

    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

串附加函式append(),將指定字串str追加到此字元序列。如果str為null,則追加4個字元"null"

該方法呼叫了父類AbstractStringBuilder的append()方法,具體實現是:

    public AbstractStringBuilder append(String str) {
        if (str == null) str = "null";
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

在此方法中使用的getChars(srcBegin,srcEnd,dst[],destBegin)函式底層,也同樣還是採用System.arraycopy(src,srcPos,dest,destPos,length)函式實現的。

    public StringBuilder insert(int offset, String str) {
        super.insert(offset, str);
        return this;
    }

串插入函式insert(),按順序將String引數中的字元插入此序列中的指定位置offset處,將此位置處原來的字元向後推,此序列將增加該引數的長度。如果str為null,則追加4個字元"null"。

該方法呼叫了父類AbstractStringBuilder的insert()方法,具體實現是:

    public AbstractStringBuilder insert(int offset, String str) {
        if ((offset < 0) || (offset > length()))
            throw new StringIndexOutOfBoundsException(offset);
        if (str == null)
            str = "null";
        int len = str.length();
        ensureCapacityInternal(count + len);
        System.arraycopy(value, offset, value, offset + len, count - offset);
        str.getChars(value, offset);
        count += len;
        return this;
    }

本質上還是採用System.arraycopy(src,srcPos,dest,destPos,length)函式實現的,只不過首先要檢驗index的合法性。

    public StringBuilder delete(int start, int end) {
        super.delete(start, end);
        return this;
    }

串刪除函式delete(),刪除此序列字串中從指定的start處開始到索引end-1處的字元,如果不存在這種字元,則一直到序列尾部。如果start等於end,則不做更改。

該方法呼叫了父類AbstractStringBuilder的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;
    }

本質上還是採用System.arraycopy(src,srcPos,dest,destPos,length)函式實現的,只不過首先要檢驗index的合法性。在檢驗end的合法性的時候,如果大於字串長度count,會將end改成count,而不會報錯

 

相關文章