typedef struct dict{
//類型特定函數
dictType *type;
//私有數據
void *privateata;
//哈希表
dictht ht[2];
//rehash 索引,rehash未進行時,值爲-1
int rehashidx;
}dict;
typedef struct dictType{
//計算哈希值的函數
unsigned int (*hashFunction)(const void *key);
//複製鍵的函
void *(*keyDup)(void *privdata, const void *key);
//複製值的函數
void *(*valDup)(void *privdata, const void *obj);
//對比鍵的函
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
//銷燬鍵的函數
void (*keyDestructor)(void *privdata, void *key);
//銷燬值的函數
void (*valDestructor)(void *privdata, void *obj);
}dictType;
// Redis 字典使用的哈希表由 dict 標識,Redis的字典使用哈希表做爲底層實現,
//一個哈希表裏面能夠有多個哈希表節點,而每一個哈希表節點就保存了字典中的一個鍵值對。
typedef struct dictht {
//哈希表數組
dictEntry **table;
//哈希表大小
unsigned long size;
//哈希表大小掩碼,用於計算索引值,老是等於size-1
unsigned long sizemask;
//該哈希表已有節點的數量
unsigned long used;
}dictht;
//哈希表節點用dictEntry標識,每一個dictEntry保存了一個鍵值對
typedef struct dictEntry {
//鍵
void *key;
//值
union{
void *val;
uint64_tu64;
int64_ts64;
} v;
//指向下個哈希表節點,造成鏈表,解決地址衝突問題
struct dictEntry *next;
} dictEntry;
rehashidx記錄了rehash目前的進度,若是目前沒有在進行rehash,那麼它的值爲-1。算法
當要將一個新的鍵值對添加到字典時,程序須要根據鍵值對的鍵計算出哈希值和索引值,而後根據索引值,將包含鍵值對的哈希節點放到哈希表數組的指定索引上。數據庫
Redis計算哈希值和索引值的方法以下:數組
#使用字典設置的哈希函數,計算哈希值服務器
hash = dict -》 type -》 hashFunction(key)數據結構
#使用哈希表的sizemask 屬性和哈希值hash,計算出索引值函數
#根據狀況不一樣,ht[x] 能夠是 ht[0] 或 ht[1]性能
index = hash & dictht -> ht[x].sizemaskui
當字典被用做數據庫的底層實現或哈希鍵的底層實現時,Redis 使用的是 Murmurhash2 算法來計算hash 值。spa
Redis 使用鏈地址法來解決鍵衝突。每一個哈希表節點有一個next指針,多個哈希表節點經過next 指針構成一個單向鏈表。由於 dictEntry 節點組成的鏈表沒有指向鏈表表尾的指針,因此爲了速度考慮,老是將新節點添加到鏈表的表頭位置(複雜度爲O(1)),排在其餘已有節點前面。(k2,v2)是新添加的節點。3d
隨着操做的不斷執行,哈希表保存的鍵值對會逐漸的增多或減小,爲了讓哈希表的負載因子維持在一個合理的閾值以內,當哈希表的鍵值對的數量太多或太少時,對哈希表進行相應的擴展或收縮。
擴展和收縮哈希表經過rehash(從新散列)進行,具體步驟以下
爲了不鍵值對過多的 rehash(涉及到龐大的計算量) 對服務器性能形成影響,服務器不是一次將ht[0] 上的全部鍵值對 rehash 到 ht[1],而是分屢次、漸進式的將 ht[0] 裏全部的鍵值對進行遷移。
漸進式hash 的步驟:
漸進式rehash 將rehash 的工做均攤到每一個添加、刪除、查找和更新中,從而避免集中rehash帶來的問題。