一,哈希表節點算法
哈希表節點使用dictEntry結構表示,每一個dictEntry結構都保存一個鍵值對。數組
typedef struct dictEntry{服務器
//鍵函數
void *key;性能
//值ui
union{指針
void *val;blog
uint64_t u64;索引
int64_t s64;hash
} v;
//指向下一個哈希表節點,造成鏈表
struct dictEntry *next;
}
二,哈希表
Redis的字典所使用的哈希表由dictht結構定義:
typedef struct dictht{
//哈西表數組
dictEntry **tables;
//哈希表大小
unsigned long size;
//哈希表大小掩碼,用來計算索引值,老是等於size-1
unsigned long sizemask;
//哈希表已經有的節點數量
unsigned long used;
}
table是一個數組,數組中的每一個元素都是一個指向dictEntry結構的指針,每一個dictEntry結構保存者一個鍵值對。
size記錄了哈希表的大小,即table數組的大小
used記錄了哈希表目前已經有的節點(鍵值對)的數量
sizemask和哈希值決定一個鍵應該被放到table數組的哪一個索引上(索引值 = hash值 / sizemask)
三,字典
1. Redis字典使用的哈希表做爲底層實現,一個哈希表裏面能夠有多個哈希表節點,每一個哈希表節點保存一個鍵值對。
Redis中的字典由dict結構表示:
typedef struct dict{
//特定類型函數
dictType *type;
//私有數據
void *privdata;
//哈希表
dictht ht[2];
//rehash索引,當rehash不在進行時,值爲-1
int rehashidx;
}
type是一個指向dictType結構的指針,每一個dictType結構保存了一些用於操做特定類型鍵值對的函數,Redis會爲不一樣用途的字典設置不一樣類型的函數
privdata保存了特定類型函數的參數
type屬性和privdata屬性時針對不一樣類型的鍵值對,爲建立多態字典而設置的
typedef struct dictType{
//計算哈希值的函數
unsigned int (*hashFunction)(const viod *key)
//複製鍵的函數
//複製值的函數
//銷燬鍵的函數
//銷燬值的函數
}
ht屬性時一個包含兩個項的數組,數組中每一個項都是一個dictht哈希表,通常狀況下,字典只使用ht[0]哈希表,ht[1]哈希表只會對ht[0]哈希表rehash時才使用
rehashidx記錄了rehash目前的進度,若是目前沒有在進行rehash,那麼它的值爲-1
2. 哈希算法
當把一個新的鍵值對添加到字典中時,程序先根據鍵值對的鍵計算出哈希值,而後計算出索引值,最後把包含新鍵值對的哈希表節點放到哈希表數組的指定索引上。
使用字典設置的哈希函數,計算key的哈希值。 hash = dict -->type --> hashFunction(key);
使用哈希表的sizemask屬性和哈希值,計算出索引值。 index = hash & dict --> ht[x].sizemask;
3.解決鍵衝突
當有兩個或兩個以上數量的鍵被分配到了哈希表數組的同一個索引上面時,咱們稱這些鍵發生了衝突。Redis的哈希表使用鏈地址法來解決鍵衝突
4.rehash
爲了讓哈希表的負載因子維持在一個合理的範圍以內,當哈希表保存的鍵值對數量太多或太少時,程序須要對哈希表的大小進行相應的擴展或收縮。擴展和收縮哈希表的工做能夠經過rehash(從新散列)操做來完成。
5.漸進式rehash
進行rehash操做時,須要將ht[0]中全部的鍵值對rehash到ht[1]上。若是哈希表中有大量的數據,只用一次rehash完成操做的話,會很是消耗服務器的性能,致使服務器在一段時間內中止服務。那麼如何避免對服務器形成影響呢? 這就須要漸進式rehash了。
在rehash進行期間,每次對字典執行添加,刪除,查找,或更新操做時,程序除了執行指定的操做外,還會順帶執行一次rehash操做(將ht[0]哈希表在rehashidx索引上的全部鍵值對rehash到ht[1]上,rehash完成後,將rehashidx值增長一)。咱們每次只rehash 哈希表ht[0]的某一個索引上的鍵值對。