字典雜湊表的實現原理 Redis雜湊表的設計與實現

2021-10-12 17:00:18 字數 3380 閱讀 2896

今天要介紹的資料結構,是redis中的雜湊表,這種資料結構是redis中非常重要的一種資料型別,可以方便的處理很多複雜場景的業務需求。

雜湊表的結構定義在 dict.h 檔案中,我們抽取**檢視一下:

如圖所示, 雜湊表是乙個結構體型別,包含四個成員屬性:

由上圖我們已經知道,雜湊表的定義中包含乙個table陣列,它的每乙個元素都是乙個指向dictentry結構型別的指標,其實這裡的dictentry便是雜湊表的節點。

雜湊表節點的結構定義在 dict.h 檔案中的較前位置,我們檢視一下**:

由上圖可知每乙個dictentry結構都是乙個健值對,且有乙個next指標,來維持節點之間的鍊錶形態。下面我們詳細看下每個欄位的具體含義:

至此,我們可以看到乙個空的雜湊結構會是下面這個樣子:

我們可以看到,在結構中存有指向dictentry 陣列的指標,而我們用來儲存資料的空間即是dictentry。

雜湊衝突

我們知道在雜湊資料結構中,key 是唯一的,但是我們存入裡面的key 並不是直接的字串,而是乙個hash 值,通過hash 演算法將字串轉換成對應的hash 值,是乙個數字,然後在dictentry 中找到對應的位置。

這時候我們會發現乙個問題,如果出現hash 值相同的情況怎麼辦?

redis 中採用了連位址法(separate chaining)來解決鍵衝突。每個雜湊表節點都有乙個next 指標,多個雜湊表節點可以使用next 構成乙個單向鍊錶,被分配到同乙個索引上的多個節點可以使用這個單向鍊錶連線起來解決hash值衝突的問題。

舉個栗子,現在雜湊表中有以下的資料:k0 和k1

我們現在要插入k2,通過hash 演算法計算到k2 的hash 值為2,即我們需要將k2 插入到dictentry[2]中:

在插入後我們可以看到,dictentry指向了k2,k2的next 指向了k1,從而完成了一次插入操作(這裡選擇表頭插入是因為雜湊表節點中沒有記錄鍊錶尾節點位置)。

字典

字典用於儲存鍵值對,可以方便的根據key值操作對應的value值。redis的字典使用雜湊表作為底層實現,乙個雜湊表裡面可以有多個雜湊表節點,而每個雜湊表節點儲存了具體的在鍵值對。redis的原始碼dict.h/dict可以看到字典的結構體定義如下:

由上圖可以知道,字典包含以下幾個字段,我們分別看下其中的含義:

通過以上的學習,我們可以知道,乙個普通狀態下的字典大致如下圖所示:

隨著字典操作的不斷執行,雜湊表儲存的鍵值對會不斷增多(或者減少),為了讓雜湊表的負載因子維持在乙個合理的範圍之內,當雜湊表儲存的鍵值對數量太多或者太少時,需要對雜湊表大小進行擴充套件或者收縮(rehash)。首先有幾個概念我們需要理解:

負載因子

這裡提到了乙個負載因子,其實就是當前已使用結點數量除上雜湊表的大小,即:

load_factor = ht[0].used / ht[0].size

雜湊表擴充套件

雜湊表收縮

雜湊表的收縮,同樣是為 ht[1] 分配空間, 大小等於 max( ht[0].used, dict_ht_initial_size ),然後和擴充套件做同樣的處理即可。

下面我們看乙個rehash的完整過程:

分配空間

因此這裡我們為ht[1] 分配 空間為8。

資料轉移

將ht[0]中的資料轉移到ht[1]中,在轉移的過程中,需要對雜湊表節點的資料重新進行雜湊值計算;資料轉移後的結果:

釋放ht[0]

將ht[0]釋放,然後將ht[1]設定成ht[0],最後為ht[1]分配乙個空白雜湊表:

自此,乙個rehash的過程,全部完成。

上面我們說到,在進行拓展或者壓縮的時候,可以直接將所有的鍵值對rehash 到ht[1]中,這是因為資料量比較小。在實際開發過程中,這個rehash 操作並不是一次性、集中式完成的,而是分多次、漸進式地完成的。

漸進式 rehash

漸進式rehash 的詳細步驟:

採用漸進式rehash 的好處在於它採取分而治之的方式,避免了集中式rehash 帶來的龐大計算量。

我們了解了字典結構的方方面面,那麼redis的雜湊型別是不是一定用字典來儲存呢?為了節省空間,儲存有兩種可能:壓縮列表 和 字典。

雜湊物件儲存的編碼轉換,當雜湊物件可以同時滿足以下2個條件時, 雜湊物件使用壓縮列表編碼:

不能滿足這兩個條件的雜湊物件需要使用 hashtable 編碼。

在以後的文章中會詳細介紹壓縮列表到底是什麼?為什麼在有些情況會採用壓縮列表來儲存物件。

最後面我們看下雜湊型別鍵可能會應用在哪些方面:

應用場景:

我們簡單舉個例項來描述下hash的應用場景,比如我們要儲存乙個使用者資訊物件資料,包含以下資訊:

另外一種可能的場景是購物車,key是使用者的id,field是不同的商品標識,值是商品的數量。這樣可以很方便的維護乙個使用者的購物車資訊。當然還有很多其他的場景,在此我們不再贅述,歡迎讀者自己探索發現。

自此,我們已經比較詳細的了解了雜湊的設計與實現,了解php的同學有沒有意識到這裡的雜湊表與php底層強大的雜湊表有什麼區別呢?

先留個懸念,後面的文章我們將會詳細對比,一一分析。

字典雜湊表的實現原理 字典雜湊表的實現原理

兩個陣列 bucket陣列 儲存key的hash桶,桶指的是把hashcode分配到一定的範圍內 entry陣列 用來儲存實現的值,它是乙個單向鍊錶,bucket總是儲存鍊錶的最後乙個元素 實現方式 通過雜湊桶來實現的k v儲存,通過key的hash碼,再進行桶計算,生成乙個在某個範圍內的值,這就是...

雜湊表 雜湊表 的實現原理

雜湊表可以表述為,是一種可以根據關鍵字快速查詢資料的資料結構。通常情況下,不論雜湊表中資料有多少,增加,刪除,改寫資料的複雜度平均都是o 1 效率非常高。如果說每乙個資料它都對應著乙個固定的位置,那我們查詢特定乙個資料時,就可以直接檢視這個資料對應的位置是否存在資料。乙個形象的例子就是學生在教室中的...

Day 8,雜湊表的原理及字典簡單實現

構建乙個確定的對映,它能把關鍵字對映到乙個唯一的儲存位置,此處的對映就叫做雜湊函式,通過這種對映得到的表就叫做雜湊表。那麼hash表有什麼優勢呢,相比於陣列有什麼優勢呢?舉個例子 假設此時有 a 這樣乙個陣列,我們要找出其中的3是在第幾個位置此時該怎麼做?我們需要通過遍歷整個a陣列,找到值等於3的索...