《Redis設計與實現》黃建宏版的讀書筆記
哈希表
- 哈希表(hash table):又叫散列表,是根據關鍵碼值進行訪問的數據結構。將關鍵碼值映射到表中的一個位置來訪問,以加快查找的速度。這個函數映射叫做哈希函數,存放記錄的數組叫做散列表。
- 哈希表常用於通過key快速的找到對應的value時使用。
- 哈希表的負載因子等於實際元素數目/哈希表的容量,負載因子越大表示衝突越大,負載因子越小,表示空間越浪費。一般負載因子位於0-0.7。
- 哈希算法:當將一個新的鍵值對添加到字典裏面時,先根據鍵值對的鍵計算出哈希值和索引值,然後根據索引值,將包含新鍵值對的哈希表節點放到哈希表數組的指定索引上面。
- 鍵衝突:當有兩個或以上數量的鍵被分配到了哈希表數組的同一個索引上時,就稱爲鍵衝突,Redis使用的是鏈地址法來解決衝突。dictht中沒有鏈表表尾的指針,爲了速度考慮,Redis將新增加的節點放到鏈表的表頭位置。
- 爲了讓哈希表的負載因子維持在一個合理的範圍內,當哈希表保存的鍵值對數量太多或者太少時,程序需要對哈希表的大小進行相應的擴展或者收縮(通過rehash完成)。
- Redis執行rehash的步驟如下:
- 爲ht[1]哈希表分配空間,空間大小取決於要執行的操作和ht[0]當前包含的鍵值對。
- 如果是擴展操作,ht[1]的大小爲第一個大於等於ht[0].used * 2 的 2的n次方。
- 如果是收縮操作,ht[1]的大小爲第一個大於等於ht[0].used 的2的n次方。
- 將保存在ht[0]中的所有鍵值對rehash到ht[1]上面。
- 當ht[0]包含的所有鍵值對都遷移到ht[1]之後,釋放ht[0],將ht[1]設置爲ht[0],並將ht[1]新創建一個空白哈希表。
- 爲ht[1]哈希表分配空間,空間大小取決於要執行的操作和ht[0]當前包含的鍵值對。
- hash 表擴展的條件,BGSAVE、BGREWRITEAOF命令會產生新的子進程,大多數操作系統都是寫時複製,爲了避免在子進程期間進行哈希表的擴展操作節省內存,Redis會提高此時擴展操作所需要的負載因子。
- 服務器沒有執行BGSAVE、BGREWRITEAOF命令時,且哈希表的負載因子大於等於1。
- 服務器執行BGSAVE、BGREWRITEAOF命令時,且哈希表的負載因子大於等於5。
- hash 收縮操作的條件:負載因子小於0.1時。
- rehash動作並不是一次性完成的,而是漸進式的,詳細步驟如下:
- 爲ht[1]分配空間
- 維護一個索引計數器變量rehashidx,並將它的值設置爲0
- 當字典執行添加、刪除、查找或更新操作時,程序除了執行指定的操作外,還會順帶將ht[0]哈希表在rehashindx索引上的所有鍵值對rehash到ht[1],當完成後,程序將rehashidx屬性的值增一
- 當ht[0]所有鍵值對都被rehash到ht[1]時,程序將rehashidx屬性值設置爲-1。
- 漸進式rehash執行期間的哈希表的刪除、查找、更新等操作會在兩個哈希表上進行,如果在ht[0]中未找到該節點,則會繼續在ht[1]中進行操作。添加操作會一律保存在ht[1]裏面,ht[0]不會進行任何添加操作。
- redis中哈希表的結構
typedef struct dictht{
// 哈希表數組
dictEntry **table;
// 哈希表大小
unsigned long size;
// 哈希表大小掩碼,用於計算索引值,總是等於size - 1
unsigned long sizemask;
// 已有節點數量
unsigned long used;
}dictht;
// dictEntry結構
typedef struct dictEntry {
// 鍵
void *key;
// 值
union {
void *val;
uint64_t u64;
int64_t s64;
} v;
// 指向下個哈希表節點,形成鏈表,用來解決鍵衝突
struct dictEntry *next;
}dictEntry;
字典
- Redis中字典的結構
typedef struct dict {
// 類型特定函數,每個dictType結構保存了一簇用於操作特定類型鍵值對的函數,Redis會用不同的字典設置不同的類型特定函數。
dictType *type;
// 私有數據,保存了需要傳給哪些特定函數的可選參數。
void *privdata;
// 哈希表,一般情況只會使用ht[0],對ht[0]進行rehash時纔會使用ht[1]
dictht ht[2];
// rehash 索引,記錄了當前rehash的進度; 當rehash未進行時,值爲-1
int rehashidx;
} dict;
typedef struct dictType {
// 計算hash函數的值
unsigned int (*hashFunction)(const void *key);
// 複製鍵的函數
void *(*keyDup)(void *privdata, const void *key);
// 複製值的函數
void *(*valDup)(void *privdata, const void *obj);
// 銷燬鍵的函數
void *(*keyDestructor)(void *privdata, const void *key);
// 銷燬值的函數
void *(*valDestructor)(void *privdata, const void *obj);
// 對比鍵的函數
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
}