redis 基礎-數據結構-字典

字典定義:

字典(dictionary), 又名映射(map)或關聯數組(associative array), 是一種抽象數據結構, 由一集鍵值對(key-value pairs)組成, 各個鍵值對的鍵各不相同, 程序可以添加新的鍵值對到字典中, 或者基於鍵進行查找、更新或刪除等操作。

字典的主要用途有以下兩個:1.實現數據庫鍵空間(key space)2.用作 Hash 類型鍵的底層實現之一,具體可以看redis 5大對象那篇文章。

字典的實現

Redis的字典使用哈希表作爲底層實現,一個哈希表裏面可以有多個哈希表節點,而每個哈希表節點就保存了字典中的一個鍵值對。

哈希表

table屬性是一個數組,數組中的每個元素都是一個指向dict.h/dictEntry結構的指針,每個dictEntry結構保存着一個鍵值對。size屬性記錄了哈希表的大小,也即是table數組的大小,而used屬性則記錄了哈希表目前已有節點(鍵值對)的數量。sizemask屬性的值總是等於size-1,這個屬性和哈希值一起決定一個鍵應該被放到table數組的哪個索引上面

一個大小爲4的空哈希表:

哈希表節點

哈希表節點使用dictEntry結構表示,每個dictEntry結構都保存着一個鍵值對。

next屬性是指向另一個哈希表節點的指針,這個指針可以將多個哈希值相同的鍵值對連接在一次,以此來解決鍵衝突(collision)的問題

字典

type屬性和privdata屬性是針對不同類型的鍵值對,爲創建多態字典而設置的:

type屬性是一個指向dictType結構的指針,每個dictType結構保存了一簇用於操作特定類型鍵值對的函數,Redis會爲用途不同的字典設置不同的類型特定函數。而privdata屬性則保存了需要傳給那些類型特定函數的可選參數。

ht屬性是一個包含兩個項的數組,數組中的每個項都是一個dictht哈希表,一般情況下,字典只使用ht[0]哈希表,ht[1]哈希表只會在對ht[0]哈希表進行rehash時使用

除了ht[1]之外,另一個和rehash有關的屬性就是rehashidx,它記錄了rehash目前的進度,如果目前沒有在進行rehash,那麼它的值爲-1

哈希算法

當有新的鍵值對添加到字典中,需要根據key 算出對應的哈希值和索引值,然後再根據索引值,將包含新鍵值對的哈希表節點放到哈希表數組的指定索引上面

Redis計算哈希值和索引值的方法如下:

舉列:我們要將一個鍵值對k0和v0添加到字典,先利用上面語句獲取hash值,然後獲取對應的索引值。


解決鍵衝突

當有兩個或以上數量的鍵被分配到了哈希表數組的同一個索引上面時,我們稱這些鍵發生了衝突(collision).

Redis的哈希表使用鏈地址法(separate chaining)來解決鍵衝突,每個哈希表節點都有一個next指針,多個哈希表節點可以用next指針構成一個單向鏈表,被分配到同一個索引上的多個節點可以用這個單向鏈表連接起來,這就解決了鍵衝突的問題。

rehash

擴展和收縮哈希表的工作可以通過執行rehash(重新散列)操作來完成,Redis對字典的哈希表執行rehash的步驟如下:

1)爲字典的ht[1]哈希表分配空間,這個哈希表的空間大小取決於要執行的操作,以及ht[0]當前包含的鍵值對數量(也即是ht[0].used屬性的值):

如果執行的是擴展操作,那麼ht[1]的大小爲第一個大於等於ht[0].used*2的2 n(2的n次方冪);如果執行的是收縮操作,那麼ht[1]的大小爲第一個大於等於ht[0].used的2 n。

2)將保存在ht[0]中的所有鍵值對rehash到ht[1]上面:rehash指的是重新計算鍵的哈希值和索引值,然後將鍵值對放置到ht[1]哈希表的指定位置上。

3)當ht[0]包含的所有鍵值對都遷移到了ht[1]之後(ht[0]變爲空表),釋放ht[0],將ht[1]設置爲ht[0],並在ht[1]新創建一個空白哈希表,爲下一次rehash做準備。

執行rehash之前的字典

爲字典的ht[1]哈希表分配空間

將ht[0]包含的四個鍵值對都rehash到ht[1]

釋放ht[0],並將ht[1]設置爲ht[0],然後爲ht[1]分配一個空白哈希表

另一方面,當哈希表的負載因子小於0.1時,程序自動開始對哈希表執行收縮操作

漸進式rehash

背景:如果哈希表裏保存的鍵值對數量不是四個,而是四百萬、四千萬甚至四億個鍵值對,那麼要一次性將這些鍵值對全部rehash到ht[1]的話,龐大的計算量可能會導致服務器在一段時間內停止服務。

爲了避免rehash對服務器性能造成影響,服務器不是一次性將ht[0]裏面的所有鍵值對全部rehash到ht[1],而是分多次、漸進式地將ht[0]裏面的鍵值對慢慢地rehash到ht[1]。

以下是哈希表漸進式rehash的詳細步驟:

1)爲ht[1]分配空間,讓字典同時持有ht[0]和ht[1]兩個哈希表。2)在字典中維持一個索引計數器變量rehashidx,並將它的值設置爲0,表示rehash工作正式開始。3)在rehash進行期間,每次對字典執行添加、刪除、查找或者更新操作時,程序除了執行指定的操作以外,還會順帶將ht[0]哈希表在rehashidx索引上的所有鍵值對rehash到ht[1],當rehash工作完成之後,程序將rehashidx屬性的值增一。4)隨着字典操作的不斷執行,最終在某個時間點上,ht[0]的所有鍵值對都會被rehash至ht[1],這時程序將rehashidx屬性的值設爲-1,表示rehash操作已完成。漸進式rehash的好處在於它採取分而治之的方式,將rehash鍵值對所需的計算工作均攤到對字典的每個添加、刪除、查找和更新操作上,從而避免了集中式rehash而帶來的龐大計算量

在漸進式rehash執行期間,新添加到字典的鍵值對一律會被保存到ht[1]裏面,而ht[0]則不再進行任何添加操作,這一措施保證了ht[0]包含的鍵值對數量會只減不增,並隨着rehash操作的執行而最終變成空表。

字典API


本文分享自微信公衆號 - 小技術君(gh_2fd927ba125d)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章