字典(dictionary),又名映射(map)或關聯數組(associative array), 它是一種抽象資料結
構,由一集鍵值對(key-value pairs)組成,各個鍵值對的鍵各不相同,程式可以将新的鍵值對
添加到字典中,或者基于鍵進行查找、更新或删除等操作。
字典的主要用途有以下兩個:
1. 實作資料庫鍵空間(key space)
2. 用作Hash 類型鍵的其中一種底層實作
實作資料庫鍵空間
Redis 是一個鍵值對資料庫,資料庫中的鍵值對就由字典儲存:每個資料庫都有一個與之相對
應的字典,這個字典被稱之為鍵空間(key space)。
當使用者添加一個鍵值對到資料庫時(不論鍵值對是什麼類型),程式就将該鍵值對添加到鍵空
間;當使用者從資料庫中删除一個鍵值對時,程式就會将這個鍵值對從鍵空間中删除;等等。
用作Hash 類型鍵的其中一種底層實作
Redis 的Hash 類型鍵使用以下兩種資料結構作為底層實作:
1. 字典;
2. 壓縮清單;
因為壓縮清單比字典更節省記憶體,是以程式在建立新Hash 鍵時,預設使用壓縮清單作為底層
實作,當有需要時,程式才會将底層實作從壓縮清單轉換到字典。
字典的實作
實作字典的方法有很多種:
最簡單的就是使用連結清單或數組,但是這種方式隻适用于元素個數不多的情況下;
要兼顧高效和簡單性,可以使用哈希表;
如果追求更為穩定的性能特征,并且希望高效地實作排序操作的話,則可以使用更為複
雜的平衡樹;
在衆多可能的實作中,Redis 選擇了高效且實作簡單的哈希表作為字典的底層實作。
dict.h/dict 給出了這個字典的定義:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<code>/*</code>
<code>* 字典</code>
<code>**</code>
<code>每個字典使用兩個哈希表,用于實作漸進式rehash ,即哈希表擴容</code>
<code>*/</code>
<code>typedef struct dict {</code>
<code>// 特定于類型的處理函數</code>
<code>dictType *type;</code>
<code>// 類型處理函數的私有資料</code>
<code>void</code> <code>*privdata;</code>
<code>// 哈希表(2 個)</code>
<code>dictht ht[</code><code>2</code><code>];</code>
<code>// 記錄rehash 進度的标志,值為-1 表示rehash 未進行</code>
<code>int</code> <code>rehashidx;</code>
<code>// 目前正在運作的安全疊代器數量</code>
<code>int</code> <code>iterators;</code>
<code>} dict;</code>
注意dict 類型使用了兩個指針分别指向兩個哈希表。
其中,0 号哈希表(ht[0])是字典主要使用的哈希表,而1 号哈希表(ht[1])則隻有在程式
對0 号哈希表進行rehash 時才使用。
哈希表實作
字典所使用的哈希表實作由dict.h/dictht 類型定義:
<code>* 哈希表</code>
<code>typedef</code> <code>struct</code> <code>dictht {</code>
<code>// 哈希表節點指針數組(俗稱桶,bucket)</code>
<code>dictEntry **table;</code>
<code>// 指針數組的大小</code>
<code>unsigned </code><code>long</code> <code>size;</code>
<code>// 指針數組的長度掩碼,用于計算索引值</code>
<code>unsigned </code><code>long</code> <code>sizemask;</code>
<code>// 哈希表現有的節點數量</code>
<code>unsigned </code><code>long</code> <code>used;</code>
<code>} dictht;</code>
table 屬性是一個數組,數組的每個元素都是一個指向dictEntry 結構的指針。
每個dictEntry 都儲存着一個鍵值對,以及一個指向另一個dictEntry 結構的指針:
<code>* 哈希表節點</code>
<code>typedef</code> <code>struct</code> <code>dictEntry {</code>
<code>// 鍵</code>
<code>void</code> <code>*key;</code>
<code>// 值</code>
<code>union</code> <code>{</code>
<code>void</code> <code>*val;</code>
<code>uint64_t u64;</code>
<code>int64_t s64;</code>
<code>} v;</code>
<code>// 鍊往後繼節點</code>
<code>struct</code> <code>dictEntry *next;</code>
<code>} dictEntry;</code>
next 屬性指向另一個dictEntry 結構,多個dictEntry 可以通過next 指針串連成連結清單,從
這裡可以看出,dictht 使用鍊位址法來處理鍵碰撞:當多個不同的鍵擁有相同的哈希值時,哈
希表用一個連結清單将這些鍵連接配接起來。
下圖展示了一個由dictht 和數個dictEntry 組成的哈希表例子:
<a href="http://s3.51cto.com/wyfs02/M01/11/91/wKioL1LVSEeTpFxrAADhOazr_ic213.jpg" target="_blank"></a>
如果再加上之前列出的dict 類型,那麼整個字典結構可以表示如下:
<a href="http://s3.51cto.com/wyfs02/M00/11/92/wKiom1LVSHmALDvWAAD7DusTTJY742.jpg" target="_blank"></a>
在上圖的字典示例中,字典雖然建立了兩個哈希表,但正在使用的隻有0 号哈希表,這說明字
典未進行rehash 狀态。
建立新字典
<a href="http://s3.51cto.com/wyfs02/M00/11/92/wKiom1LVSOLQhSS3AAD7vuy5spI163.jpg" target="_blank"></a>
新建立的兩個哈希表都沒有為table 屬性配置設定任何空間:
ht[0]->table 的空間配置設定将在第一次往字典添加鍵值對時進行;
ht[1]->table 的空間配置設定将在rehash 開始時進行;
添加鍵值對到字典
根據字典所處的狀态,将一個給定的鍵值對添加到字典可能會引起一系列複雜的操作:
如果字典為未初始化(也即是字典的0 号哈希表的table 屬性為空),那麼程式需要對0
号哈希表進行初始化;
如果在插入時發生了鍵碰撞,那麼程式需要處理碰撞;
如果插入新元素使得字典滿足了rehash 條件,那麼需要啟動相應的rehash 程式;
當程式處理完以上三種情況之後,新的鍵值對才會被真正地添加到字典上。
整個添加流程可以用下圖表示:
<a href="http://s3.51cto.com/wyfs02/M02/11/91/wKioL1LVShjCy-JnAAFF_PDoi_k441.jpg" target="_blank"></a>
<a href="http://s3.51cto.com/wyfs02/M01/11/92/wKiom1LVSlbhsZeCAABfLA8zDRw223.jpg" target="_blank"></a>
在接下來的三節中,我們将分别看到添加操作如何在以下三種情況中執行:
1. 字典為空;
2. 添加新鍵值對時發生碰撞處理;
3. 添加新鍵值對時觸發了rehash 操作;
~~每次遇到指針,就有點頭大,明天繼續吧!
本文轉自shayang8851CTO部落格,原文連結:http://blog.51cto.com/janephp/1351687,如需轉載請自行聯系原作者