Redis設計於實現之字典

2021-10-25 09:15:26 字數 3107 閱讀 4581

簡介

字典實現

字典的實現是以雜湊表作為它的底層實現,乙個雜湊表可以有多個雜湊表節點,每個節點儲存了字典中的乙個鍵值對。

1.雜湊表節點

key就是鍵,v就是鍵中的值(可以是指標,unit64_t 整數, int64_t s64整數),next是將另外乙個雜湊值相同的鍵值對連線在一起的指標(為了解決衝突)

typedef struct dictentryv;

struct dictentry *next;

}

2.雜湊表

table屬性是乙個陣列,陣列中每個元素都指向乙個雜湊表節點 ,每個雜湊表節點都儲存著乙個鍵值對。

size記錄了雜湊表的大小,也就是table陣列的大小。

used屬性記錄雜湊表目前已有雜湊表節點(鍵值對)的數量。

sizemask總是等於size-1(這個屬性和雜湊值一起決定乙個鍵應該被放到table的那個索引上)。

typedef struct dicthtdictht;
3.字典

type和pribdata是配套的,針對不同型別的鍵值對,為建立多型字典而設定的。

type指向dicttype結構的指標,每乙個dicttype裡都儲存了一簇用於操作特定型別鍵值對的函式(為用途不同的字典設定不同的型別特定函式)。

privdata儲存了需要傳給那些型別特定函式的可選引數(也就是在dicttype結構體中的引數)。

ht包含了兩項陣列,每個項都是dictht雜湊表,字典只使用ht[0]雜湊表,ht[1]只會在ht[0]rehash時使用,除了ht[1]之外另乙個和rehash(重新雜湊)有關的就是rehashidx(記錄rehash進度,若沒在進行rehash則值為-1).

typedef struct dictdict;
/* 儲存一連串操作特定型別鍵值對的函式 */

typedef struct dicttype dicttype;

這就是字典實現的資料結構,如果要資料庫效能好,還是要用一些效能較好的演算法,redis使用的murmurhash2演算法來計算鍵的雜湊值(資料庫的底層實現或者雜湊鍵的底層實現)。

雜湊演算法

在我們需要把乙個新的鍵值對加入到字典裡,程式得先根據鍵值對的鍵計算出雜湊值和索引值,然後根據索引,將新鍵值對的雜湊表節點方法雜湊表陣列的指定索引(位置)上,這都是由雜湊演算法來完成的。雜湊演算法的設計推理就不寫了,因為這個是乙個很複雜的過程。

redis計算雜湊值和索引值的方法

//用字典設定的雜湊函式計算key的雜湊值

hash = dict->type->hashfunction(key);

//利用雜湊表的sizemask和雜湊值來計算出索引值,h[x]可以是h[1]或h[0]因情況而定。

index = hash & dict->ht[x].sizemask;

這種演算法的優點就是:對於輸入有規律的鍵仍能給出乙個很好的隨機分布性而且計算速度也很快!但是呢,這種演算法可能會出現衝突,因此要避免衝突就得由個解決衝突的辦法?(在前面提到過)

解決鍵衝突

什麼是衝突:因為演算法執行時會有可能多個鍵被分配到雜湊陣列的同乙個索引上。

redis中解決鍵衝突採用的是鏈位址法,每個節點都有乙個next指標,構成衝突的節點可以用next指標構成乙個單鏈表來共同占有同乙個索引。這個解決方法也是解決衝突比較經典的方法,也是比較簡單的方法。

rehash

rehash是什麼?為什麼要rehash?

rehash是重新雜湊的意思,因為在不斷的執行中,雜湊表儲存的鍵值對在逐漸增多或減少,為了讓雜湊表的負載因子(load factor)維持在合理範圍內,當雜湊表中的鍵值對過多或者過少時,需要對錶的大小進行擴充套件或收縮

雜湊表執行rehash的步驟:

1.為字典的ht[1]分配空間,此雜湊表的大小取決於要執行的操作和h[0]包含的鍵值對的數量(ht[0].used)

2.將儲存在ht[0]中的鍵值對到ht[1]上:重新計算雜湊值和索引,然後將鍵值對放到ht[1]雜湊表的指定位置上。

3. ht[0]遷移到ht[1]後,ht[0]變為空表然後釋放掉,然後再將ht[1]設定為ht[0],並再ht[1]位置上新建乙個空白的雜湊表,供下一次rehash使用。

雜湊表的自動擴充套件與收縮

如果在負載因子不合理時沒有進行手動的rehash的話,那系統會在某些條件成立下自動進行擴充套件或收縮。

雜湊表的負載因子求法:負載因子=以儲存節點數量/雜湊表大小

load_factor = ht[0].userd/ht[0].size
漸進式rehash

其實在擴充套件或者收縮雜湊表的時候並不是一次性,集中性的執行的,而是分多次,漸進式地完成的。

漸進式的詳細步驟:

1.為ht[1]分配空間,此時字典同時有ht[0]和ht[1]兩個雜湊表。

2.在字典中維持乙個索引計數器變數rehashidx,並設為0,表示rehash正式開始。

3.在rehash期間,每次對字典進行新增,刪除,查詢,更新時,程式除了執行指定操作以外,還會順帶將ht[0]雜湊表在rehashidx索引上的所有鍵值對rehash到ht[1]上,rehash完成,然後rehashidx+1。

4.直到全部內移到ht[1],這時rehashidx屬性的值設為-1,表示rehash操作完成。

採取分而治之的方式,將rehash鍵值對所需的工作均攤到對字典的增刪改查上,避免了集中式rehash帶來的龐大計算量。

漸進式rehash執行期間的雜湊表操作

因為在rehash期間字典會同時使用ht[0]和ht[1],因此,增刪改查會在兩個雜湊表上進行,比如查詢操作,先對0表掃瞄如果沒找到,就再從1表裡找。注意的是,如果在此期間進行插入操作的話,那就會插入到1表,而不是0表。因為插入到0表就沒意義了等於浪費體力。也保證了0表只減不增。

Redis設計於實現(二)

簡介 字典實現 字典的實現是以雜湊表作為它的底層實現,乙個雜湊表可以有多個雜湊表節點,每個節點儲存了字典中的乙個鍵值對。1.雜湊表節點 key就是鍵,v就是鍵中的值 可以是指標,unit64 t 整數,int64 t s64整數 next是將另外乙個雜湊值相同的鍵值對連線在一起的指標 為了解決衝突 ...

Redis的設計與實現 字典

參考部落格 絕大多數語言中的字典底層實現基本上都是雜湊表。雜湊表中用 負載因子 來衡量雜湊表的空 滿程度。為了讓負載因子在一定的合理範圍之內,提高查詢的效能,一般的做法是讓雜湊表擴容,然後rehash一把。but,擴容也不一定就能解決負載因子過大的問題。redis作為一款成熟的非關係型資料庫,肯定有...

實現動態表頭 Redis的設計於實現(1)

redis的預設字串型別為sds。只會使用c字串作為字面量 比如列印 struct sdshdr sds遵循c字串以空字串結尾的慣例,儲存空字串的1位元組空間不計入sds的len屬性裡面。優點 字串的複雜度為o 1 len可直接返回長度。杜絕緩衝區溢位 sds的空間分配杜絕了溢位可能。減少字串修改帶...