1. 程式人生 > >STL深入探究(二、容器的底層實現)

STL深入探究(二、容器的底層實現)

1 前言

上一篇 STL深入探究(一、空間配置器)我詳細總結了SGI STL採用的空間配置機制,這一篇來總結一下stl容器的底層實現機制。

2 序列式容器

2.1 Vector

Vector實現方式類似於“陣列”,與array的資料安排和操作方式非常類似,兩者唯一的差別就在於空間運用的靈活性。 array是靜態空間,一旦配置了就不能改變,要想換一個大點的空間,就必須由客戶自己實現:(1)配置一塊新空間;(2)將元素從舊址一一搬到新址;(3)釋放原空間。 而vector是動態空間,隨著元素的加入,它的內部機制會自行擴充空間以容納新元素。

2.1.1 vector的資料結構

vector的資料結構非常簡單:線性連續空間,以兩個迭代器start、finish分別指向配置得來的連續空間中目前已被使用的範圍,並以迭代器end_of_storage指向整塊連續空間的尾部。 為了降低空間配置的速度成本,vector的實際配置大小可能會比客戶端需求量更大,已備將來可能擴充。

2.1.2 vector 構造與記憶體管理

vector預設使用std::alloc作為空間配置器,並另外定義了一個data_allocator為了更方便以元素大小為配置單位。 當以push_back()函式將新元素插入vector尾端,該函式首先檢查是否還有備用空間,如果有就直接在備用空間上構造元素,並調整迭代器finish,使得vector變大;如果沒有備用空間,就擴充空間(重新配置(需求的兩倍)、移動資料、釋放原空間)。 對於vector支援的動態增加大小,並不是在原空間之後接續新空間(因為並不能保證原空間之後尚有足夠的可供配置的新空間),而是以原大小的兩倍另外配置一塊較大空間,然後將原空間內容拷貝過來,再開始在原內容之後構造新元素,並釋放原空間。 所以說,對vector的任何操作,一旦引起空間重新配置,指向原vector的所有迭代器就都失效。

2.2 list

相比較於vector的連續線性空間,list就比較複雜,每次插入或者刪除一個元素都會獨立的配置或釋放一個元素空間。list對於空間利用非常精準,不會浪費。 list是環狀雙向連結串列,我們在資料結構中都曾學習過它的特性與使用方法,不再多說。值的一提的是,list由於其雙向性,必須提供前移與後移的能力,所以提供BidirectionalIterators,而vector提供的是Random Access Iterators。 注: list有一個重要性質,插入(insert)和接合(splice)操作都不會造成原有的list迭代器失效。list的元素刪除(erase)操作僅僅“指向被刪除元素”的迭代器失效,其它迭代器不受影響。 vector並做不到這一點,因為vector的插入操作可能引起空間重新配置,這導致原有的迭代器全部失效。

另外,SGI list不僅僅是一個雙向連結串列,而且是一個環狀雙向連結串列,所以它只需一個指標即可完整表示整個結構。

2.3 deque

vector底層採用的是一個數組來實現,list底層採用的是一個環形的雙向連結串列實現,而deque則採用的是兩者相結合,所謂結合,並不是兩種資料結構的結合,而是某些效能上的結合。我們知道, vector支援隨機訪問,而list支援常量時間的刪除,deque支援的是隨機訪問以及首尾元素的插入刪除 。
deque與vector的最大差異,一在於deque允許常數時間內對起頭端進行元素的插入和刪除,二在於deque沒有所謂的容量概念,因為它是以分段連續空間組合而成,隨時可以增加一段新的空間並連線起來。換句話說,像vector那樣因舊空間不足而重新配置更大空間,複製元素,釋放原空間的事情,deque是不會出現的。也因此,deque不需要提供所謂的空間保留。

2.3.1 deque的資料結構


如上圖所示,deque是由一段一段的定量連續空間構造。一旦有必要在deque的前端或尾端增加新空間,便配置一段連續空間,串接在整個deque的頭部或尾部。 deque採用一塊所謂的map(不是STL的map容器)作為主控,這裡的map也是一塊連續空間,其中每個元素為一個節點(也是deque的迭代器),指向另一段較大的連續線性空間,稱為緩衝區,緩衝區才是deque的儲存空間主體。

2.3.2 deque的迭代器

下面,來看一下deque的迭代器設計。
舉例來說,如果deque中儲存20個元素,每個緩衝區大小為8,則需要20/8=3個緩衝區,所以map中會運用3個節點。deque的begin()和end()始終會返回map中節點的頭和尾,名為start和finish,其中,start的cur指向第一個緩衝區中的首元素,finish的cur指向最後一個緩衝區的最後一個元素的下一位置。

2.3.3 deque的構造和記憶體管理

deque的緩衝區擴充操作比較繁雜,此處僅僅擷取《STL原始碼剖析》的幾張圖展示,具體細節見書籍P153-166。




注:上面的連環圖解,充分展示了deque容器的空間處理。那麼現在就出現一個問題,什麼時候map需要重新整治?      如果無窮盡的向deque中儲存資料,map所含有的緩衝區肯定會填滿的。也就是說,當map的前端節點或後端節點備用空間不足時,map便也需要重新配置,依然是經過三個步驟,配置更大的、拷貝原來的、釋放原空間。