C#基礎知識梳理系列九:StringBuilder

來源:互聯網
上載者:User
摘 要

幾乎所有的資料都告訴我們,在頻繁進行字串拼接操作的時候推薦使用StringBuilder,因為它提供更優秀的效能,不辭勞苦的我們也寫樣本驗證過,確實如傳說的那樣!但為什麼StringBuilder 在操作字串會有優異的表示呢?它真是像很多資料中所說“每次新追加進來字串和舊有字串總長度超設定容量時,會新鍵一個數組存放串字元,並且丟棄原有舊數組”嗎?這一節我們來看個究竟。

第一節 StringBuilder

在前面的章節中我們已經知道,字串是由字元組成,由於字串具有不可變性,所以每一次對字串的變動都會重新分配記憶體、建立一個字串對象、丟棄舊對象,在重新分配記憶體過程可能會導致記憶體回收,這一系列的操作,會大大損傷效能。為瞭解決這個問題,FCL提供了一個System.Text.StringBuilder類來構造管理字串。就像它的名字一樣,它是一個構造器,提供了對字串的追加、移除和替換等功能,向StringBuilder對象追加字串時,實際上在內部是轉為追加字元。System.Text.StringBuilder類有幾個重要的欄位、屬性和方法,我們一個一個來看。

 

第二節 欄位和屬性

(1) internal char[] m_ChunkChars

儲存StringBuilder所管理著的字串中的字元。系統預設初始化它的長度為16,當新追加進來的字串長度與舊有字串長度之和大於該字元數組容量時,新建立字元數組容量會增加到“2的(n+1)次冪”(假如當前字元數組容量為2的n次冪)。

(2) internal int m_ChunkLength;

字元數組m_ChunkChars內的實際字元個數,系統預設m_ChunkChars的容量是16。

(3) internal int m_ChunkOffset;

字元定位的位移量

(4) internal StringBuilder m_ChunkPrevious;

內部的一個StringBuilder對象,追加的字串長度和舊字串長度之合大於字元數組m_ChunkChars的最大容量時,會根據當前的(this)StringBuilder建立一個新的StringBuilder對象,將m_ChunkPrevious指向新建立的StringBuilder對象。這個是關鍵。

(5) public int Length;

當前StringBuilder對象實際管理的字串長度。Length = m_ChunkLength + m_ChunkOffset

(6) public int Capacity;

設定或擷取字元數組m_ChunkChars的最大容量。

第三節 追加字串時StringBuilder的內部工作

建立一個新的StringBuilder對象後,字元數組m_ChunkChars最大容量被初始化為16,向StringBuilder對象追加字串,如果追加前後的字串總長度小於等於16,則將新追加的字串的字元複製到m_ChunkChars數組;如果追加前後的字串總長度大於16,則先用新字元將當前m_ChunkChars填滿,再以當前對象(this)為基礎構造一個StringBuilder對象,並且將m_ChunkPrevious指向這個新建立的StringBuilder對象,然後將Capacity設定為2的(n+1)次冪32,重新初始化字元數組m_ChunkChars且容量為2的(n+1)次冪32(注意:這個不一定),然後將剛才剩餘的字元複製到最新的字元數組m_ChunkChars。每一次追加字串都會執行上面類似的步驟。下面我們來看一下這個過程,為了方便示範,我們在建立一個StringBuilder對象後,先設定容量為2。如下代碼:

            StringBuilder strBuilder = new StringBuilder();            strBuilder.Capacity = 2;

看一下初始化後的結果:

可以看到最大容量Capacity為2,由於未向其追加字串,所以字元數組m_ChunkChars的元素為空白,m_ChunkPrevious是null。

A)接著我們向其追加一個字串”a”:

可以看到,新添加的字元a被放到了字元數組的0號位置,字元數組內元素個數為1。

B)接著追加一個字元b:

此時是將新字元b放到了字元數組1號位置,很顯然字元數組的有效長度m_ChunkLength增加1後值為2,此時的m_chunkPrevious依然保持著null。

C)接著我們再添加一個字元c:

可以看到,strBuilder的字元容量Capacity已經變成2的(1+1)次冪4。因為原先長度為2的數組m_ChunkChars已經無法裝載長度為3的字串,所以要創新建立一個數組來擴容,但是這裡使用舊有容量(值為2)建立的數組已經中以容納新加進來的字串 c ,所以m_ChunkChars數組依然被初始化為容量為2的數組。由於strBuilder內已經有3個字元,Length=m_ChunkLength+m_ChunkOffset,所以Length為3,最新的字元c已經放到了新數組m_ChunkChars的0號位。最主要的是欄位m_ChunkPrevious已經不空null了,它已經指向截止到B)步驟的strBuilder對象,這個指向可以通過StringBuilder內部代碼看的出來:

        private void ExpandByABlock(int minBlockCharCount)        {            if ((minBlockCharCount + this.Length) > this.m_MaxCapacity)            {                throw new ArgumentOutOfRangeException("requiredLength", Environment.GetResourceString("ArgumentOutOfRange_SmallCapacity"));            }            int num = Math.Max(minBlockCharCount, Math.Min(this.Length, 0x1f40));            this.m_ChunkPrevious = new StringBuilder(this);            this.m_ChunkOffset += this.m_ChunkLength;            this.m_ChunkLength = 0;            if ((this.m_ChunkOffset + num) < num)            {                this.m_ChunkChars = null;                throw new OutOfMemoryException();            }            this.m_ChunkChars = new char[num];        }

事實上初始化m_ChunkPrevious在前,建立新的字元數組m_ChunkChars在後,最後才是複製字元到數組m_ChunkChars中。

D)接著我們連續添加兩個字元d和e:

在上一步驟C)的時候容量Capacity是4,字元數組還有一個空位置,所以當我們添加字元d時還可以用該數組,並不需要遷移對象和重建數組。但是在添加字元e的時候,由於總字元個數為5(abcde)已經超出了Capacity的4,所以此時會執行類似C)的步驟,最關鍵的兩行代碼:

this.m_ChunkPrevious = new StringBuilder(this);this.m_ChunkChars = new char[num];

需要說明,為了節省記憶體,StringBuilder內部並不一定是每次擴容m_ChunkChars真的按照2的(n+1)次冪進行計算,它是根據舊有字串和新追加字串的總長度和上一次容量的差來進行擴容:

int num = Math.Max(minBlockCharCount, Math.Min(this.Length, 0x1f40));this.m_ChunkChars = new char[num];

歸根結底,StringBuilder是在內部以字元數組m_ChunkChars為基礎維護一個鏈表m_ChunkPreviou。

 

第四節 常用方法

(1) Append方法及重載

public unsafe StringBuilder Append(string value)

向StringBuilder追加新元素,由於在內部使用了指標,所以這裡用了unsafe。它有18個重載,無論哪個重載方法,最終都是將新值轉為字元進行添加。類似的還有AppendFormat系列方法。

(2) Insert方法及重載

public unsafe StringBuilder Insert(int index, string value)

向指定位置插入字串。

(3) Replace方法及重載

public StringBuilder Replace(string oldValue, string newValue);

使用新字串替換與oldValue匹配的字串,它有3個重載。

(4) Remove方法

public StringBuilder Remove(int startIndex, int length);

從指定索引位移除指定數量的字元,它沒有重載。方法Insert、Replace和Remove都是對內部字元數組m_ChunkChar和鏈表中m_ChunkPrevious內的字元數組m_ChunkChar操作,StringBuilder內部實現有點“繞”,感興趣的可以自行去研究研究。

(5) ToString方法

public override string ToString();

StringBuilder重寫了基類的ToString()方法用來擷取StringBuilder對象的字串表示,它是將鏈表m_ChunkPrevious中的字元數組m_ChunkChars及當前StringBuilder對象的字元數組m_ChunkChar中的字元轉成String對象返回,這一步是建立一個新的String對象,所以對這個String對象(ToString()的結果)的操作不會影響到StringBuilder對象內部的字元。

還有一個方法與ToString()方法類似:

public string ToString(int startIndex, int length);

將指定位置及指定長度的字元轉為字串。

 

小 結
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.