Redis裏面的字典,又叫符號表、關聯數組、或映射,是一種用於保存key-value鍵值對結構的抽象數據結構,類似於java中的hashmap。
但是C語言裏面還是沒有這種數據結構> – <!哈哈,所以Redis自己構建了字典的實現。
Redis裏的字典應用介紹:
- Redis的數據庫就是使用字典來作爲底層實現,增刪改查也是構建在對字典的操作之上的。
- 字典是哈希鍵的底層實現之一,當一個哈希鍵的鍵值對比較多,或者鍵值中的元素字符串比較長的時候,Redis就會使用字典作爲哈希鍵的底層實現。
- 本篇主要寫字典的實現,後續繼續寫的話會寫字典在Redis中的應用。
字典的實現
Redis的字典底層實現是哈希表。一個哈希表裏有多個哈希節點。每個哈希節點保存着字典中的一個鍵值對。
1. 哈希表
- table屬性:是一個數組,數組中的每個元素都是指向dictEntry結構的指針,每個dicEntry結構都保存一個鍵值對。
- size屬性:記錄着哈希表的大小,也是數組的大小。
- used屬性:記錄着該哈希表已有節點的數量。
- sizemask屬性:總是等於size - 1,該屬性和哈希值決定一個鍵應該被放到數組table中的哪個位置。
2. 哈希表節點
- key屬性:保存鍵值對中的鍵。
- v屬性:保存鍵值對中的值,也可以是一個指針,或者是一個uint64_t整數,或者是一個int64整數。
- next屬性:是指向用於另外一個哈希表節點的指針,這樣可以將多個哈希值相同的鍵值連接在一起,用來解決鍵的衝突問題。
下圖展示如何使用next指針解決將兩個索引值相同的鍵連接在一起。
3. 字典
- type屬性:是一個指向dictType結構的指針,每個dictType結構保存了用於操作特定類型的鍵值對的函數。
- privdata屬性:保存了需要傳給那些類型特定函數的可選參數。
- ht屬性:包含兩個項的數組,每個項都是一個哈希表。
- trehashidx屬性:它記錄了rehash的目前進度,如果目前沒有進行rehash,值則爲-1。
- type屬性和privdata屬性是針對不同類型的鍵值對,爲創建多態字典而設置。
dictType結構
下圖爲一個普通狀態的字典
4.哈希算法
當新的一個鍵值對要添加到字典時,程序首先算出鍵值對中的鍵的哈希值和索引值,然後再根據索引值將鍵值對的哈希表節點放到哈希數組指定的索引上。計算哈希值和索引值的步驟如下
5.解決哈希衝突
當有兩個或者多個鍵值對的鍵被分配到哈希表數組中的同一個索引上,就會發生哈希衝突問題。Redis的哈希表採用拉鍊法,也稱爲鏈地址法來解決哈希衝突。每個哈希表節點都有一個next的指針,多個哈希表節點可以利用next指針形成一個單鏈表,當多個節點被分配到同一個索引上時就能解決哈希衝突問題。dictEntry節點組成的鏈表沒有指向鏈表表尾的指針,程序會將新節點放在已有節點的前面,複雜度爲O(1)。
下面兩張圖片展示解決哈希衝突的問題
未發生哈希衝突時
插入一個鍵爲k2,值爲k2的鍵值對時發生哈希衝突,程序將新節點添加到鏈表的表頭位置。排在其他節點的前面。
6.rehash
隨着我們的操作不斷執行,哈希表裏的鍵值對會逐漸增多或者減少,當程序的哈希表鍵值對太多或者太少的時候,程序需要對哈希表的大小進行擴展或者減少。
Redis對哈希表的執行rehash步驟簡介:
- 爲字典的ht[1]哈希表分配空間,哈希表的空間大小取決於要執行的操作以及ht[0]當前包含的鍵值對數量。
- 將保存在ht[0]上的鍵值對rehash到ht[1]上,重新計算哈希值和索引值,然後將所有鍵值對放置到ht[1]哈希表的指定索引上。
- 當ht[0]的所有鍵值對都轉移到ht[1]後,程序會釋放ht[0],將ht[1]設置成ht[0],並在ht[1]新創建一個空表,爲下一次rehash做準備。
漸進式rehash
rehash這個操作不是一次性完成的,而是分多次,漸進式完成的。爲什麼呢?
舉個例子:如果ht[0]只保存6個鍵值對,那麼Redis可以在瞬間就把這些鍵值對rehash到ht[1]上,但是如果是幾百萬,幾千萬,幾億呢?要是一次性把所有鍵值對都rehash到ht[1]上的話,這麼龐大的計算量會導致Redis在一段時間內停止服務。
漸進式rehash步驟簡介:
- 爲ht[1]分配空間,字典這時同時持有ht[0]和ht[1]兩張表。
- 字典中的索引計數器rehashidx設置爲0,表示rehash操作正式開始。
- 在進行漸進式的rehash操作期間,對字典的刪除、添加、修改、查找操作會在兩個哈希表進行,比如查找,先查詢ht[0],沒找到就去查找ht[1]。新增操作只會被保存到ht[1]中,而ht[0]不再進行任何的新增操作,只會出現只減不增的情況,並隨着rehash的執行完成變成空表。
- rehash操作完成時,rehashidx屬性的值會被設置成-1。表示rehash完成。
以上是簡單概述一下rehash的操作流程,不懂的建議還是百度一下看看,畢竟自己一開始看的時候也不是很懂,0-0,看多了才慢慢理解。。。
字典的自我總結
寫了這麼多屁話,是時候總結一波了!!!
- Redis的字典底層是使用哈希表實現的,每個字典裏有兩個哈希表,一個是平時使用,另外一個是執行rehash操作的時候使用。
- 哈希表使用拉鍊法來解決多個鍵值使用哈希算法計算出爲同一個索引上時的哈希碰撞問題,讓多個相同索引的鍵值對形成一個單鏈表。
- 在對哈希表進行擴展和收縮操作時,程序會對現有的哈希表裏的所有鍵值對rehash到新的哈希表裏去,但是該操作不是一次性的,是漸進式的。