Redis原始碼學習4 基本資料結構之字典

2021-06-22 07:54:05 字數 4447 閱讀 4766

redis是乙個鍵值對資料庫,在很多地方用到字典。

redis

字典的實現採用的是比較經典的雜湊表方式實現的。貌似跟

memcached

的方法有點像,很久之前看過部分

memcached

,現在忘得差不多了。

redis

的字典定義如下:

[cpp]view plain

copy

/** 字典

** 每個字典使用兩個雜湊表,用於實現漸進式 rehash

*/typedef

struct dict  dict;  

/** 雜湊表

*/typedef

struct dictht  dictht;  

/** 雜湊表節點

*/typedef

struct dictentry  v;  

// 鏈往後繼節點

struct dictentry *next;  

} dictentry;  

使用2個雜湊表,這是為了後面要說到的擴容準備的。每個雜湊表如果有衝突,則採用鍊錶方式解決。雜湊表節點的指標陣列為

table

字段,注意這是個兩級指標,我習慣這樣看

dictentry *table

,即table

陣列的每個值都是乙個指向

dictentry

結構體的指標。其他三個字段就是指標陣列大小、長度掩碼以及當前節點數。比如長度為

4的話,掩碼就是

3。雜湊表節點則是乙個簡單的單項鍊表,儲存有鍵、值以及後繼節點的指標。當不同的鍵雜湊後的值發生衝突時,採用鍊錶連線。

[cpp]view plain

copy

dict *dictcreate(dicttype *type,  

void *privdataptr)  

/** 初始化字典

** t = o(1)

*/int _dictinit(dict *d, dicttype *type,  

void *privdataptr)  

/** 重置雜湊表的各項屬性

** t = o(1)

*/static

void _dictreset(dictht *ht)    

初始化過程需要注意的是type

變數的設定,

dicttype

結構體型別的

type

是涉及到字典儲存物件的一系列的操作方法,有點多型的味道,不同的物件賦值的

type

不同。[cpp]view plain

copy

/** 新增給定 key-value 對到字典

** t = o(1)

*/int dictadd(dict *d, void *key, void *val)  

dictentry *dictaddraw(dict *d, void *key)    

新增鍵值對到字典主要的執行方法在dictaddraw函式。首先會判斷字典當前是否正在

rehash

,如果是,則呼叫_dictrehashstep(d)方法rehash雜湊表中

table

陣列的乙個桶中元素。

redis

的字典採用的是漸進式

rehash

方法,即在擴充套件雜湊表的時候不會一次性將所有的元素都從老的雜湊表移重新雜湊到新的雜湊表中,因為這樣會使得本次操作延遲過長。

rehash標識是字典中的rehashidx變數,初始為-1表示沒有

rehash

,而隨著

rehash

的進行,這個值會設定為當前雜湊到的

table

項。在每次擴容雜湊表時,就會設定

rehashidx

標識。而從

dictaddraw

方法看到,在新增鍵值對到字典時,首先要呼叫dictkeyindex(d, key)方法根據key

計算新增鍵值對的索引,即應該是

table

中的第幾項,然後根據是否在

rehash

將該元素新增到正確的雜湊表中,並更新字典的節點數目。最後設定

key對應的

value

是在dictsetval(d, entry, val)中完成。

在根據key

計算索引的方法dictkeyindex(d, key)中,首先會判斷雜湊表是否需要擴容:1)如果是第一次新增元素,則需要建立乙個新的雜湊表,大小設定為4,並設定

rehashidx

變數為0。 2

)如果使用的節點數大於

table

的大小而且dict_can_resize 為真(或者已用節點數除以雜湊表大小之比大於dict_force_resize_ratio),則也需要擴容雜湊表為當前使用節點數的2倍大小。(注:如果雜湊表本身在

rehash

過程中,擴容函式會直接返回,不會執行擴容)。然後查詢字典的兩個雜湊表查詢

key(如果雜湊表此時沒有

rehash

,則只需要查詢

ht[0].table

對應的項即可),如果已經存在,則返回

-1,否則返回

key在

table

中的索引值。

漸進式rehash

是在擴容後下次

dictadd

的時候開始執行,每次移動

table

中的乙個項的元素到新的雜湊表中。如第一次移動

ht[0].table[0]

,第二次

ht[0].table[1]

中的所有元素,其實每乙個項都是乙個鍊錶。如果有安全迭代器,則是不能夠

rehash 

的。漸進式

rehash

跟memcached

很類似的說。

[cpp]view plain

copy

/** 執行 n 步漸進式 rehash 。

** 如果執行之後雜湊表還有元素需要 rehash ,那麼返回 1 。

* 如果雜湊表裡面所有元素已經遷移完畢,那麼返回 0 。

** 每步 rehash 都會移動雜湊表陣列內某個索引上的整個鍊錶節點,

* 所以從 ht[0] 遷移到 ht[1] 的 key 可能不止乙個。

** t = o(n)

*/int dictrehash(dict *d, int n)   

/* note that rehashidx can't overflow as we are sure there are more

* elements because ht[0].used != 0 */

assert(d->ht[0].size > (unsigned)d->rehashidx);  

// 移動到陣列中首個不為 null 鍊錶的索引上

while(d->ht[0].table[d->rehashidx] == null) d->rehashidx++;  

// 指向煉表頭

de = d->ht[0].table[d->rehashidx];  

// 將鍊錶內的所有元素從 ht[0] 遷移到 ht[1]

// 因為桶內的元素通常只有乙個,或者不多於某個特定比率

// 所以可以將這個操作看作 o(1)

while(de)   

// 設定指標為 null ,方便下次 rehash 時跳過

d->ht[0].table[d->rehashidx] = null;  

// 前進至下一索引

d->rehashidx++;  

}  // 通知呼叫者,還有元素等待 rehash

return 1;  

}  

[cpp]view plain

copy

/** 返回在字典中, key 所對應的值 value

** 如果 key 不存在於字典,那麼返回 null

** t = o(1)

*/void *dictfetchvalue(dict *d, const

void *key)   

首先呼叫dictfind函式查詢字典key是否存在,存在則呼叫

dictgetval

函式獲取值。判斷

key是否存在是通過dictcomparekeys函式判定,而該函式在dicttype結構體中賦值。如果是

rehash

狀態,則需要查詢兩個雜湊表並返回

key對應的

dictentry項。

dictgetval函式則是返回

dictentry

項的val

項即可。

字典還有其他的函式,暫時就不分析**了,後面有用到再細看。

Redis原始碼學習4 基本資料結構之字典

redis基本資料結構 字典 字典概念 相關函式 建立字典 新增鍵值對到字典 獲取元素值 其他參考資料 redis是乙個鍵值對資料庫,在很多地方用到字典。redis 字典的實現採用的是比較經典的雜湊表方式實現的。貌似跟 memcached 的方法有點像,很久之前看過部分 memcached 現在忘得...

Redis原始碼學習3 基本資料結構之雙向鍊錶

楔子雙向鍊錶定義 基本函式 listcreate建立列表 listrelease釋放列表 listaddnodehead在表頭新增節點 listaddnodetail在表尾新增節點 listinsertnode在指定節點前後插入節點 listdelnode刪除鍊錶中指定節點 迭代器及相關函式 迭代器...

Python原始碼學習筆記(1 基本資料型別)

python原始碼剖析 這本書相當好。我用python也有幾個月時間了,這時候讀python原始碼,會對提高c語言水平 python水平 演算法基礎都有相當的幫助。python原始碼剖析.chm 這個檔案。學習心得嘛,就是多看多想,有問題的時候再除錯python原始碼驗證想法。robert chen...