字典:(符號表)
字典就是一個存儲kv的存儲結構,類似與c++的map,redis數據庫的底層就是使用字典實現的
除了數據庫,字典也是哈希鍵的底層實現
字典使用哈希表實現,哈希表中存儲的都是kv結構
typedef struct dictht { // 哈希表數組 dictEntry **table; // 哈希表大小 unsigned long size; // 哈希表大小掩碼,用於計算索引值 // 總是等於 size - 1 unsigned long sizemask; // 該哈希表已有節點的數量 unsigned long used; } dictht;
sizemask和哈希值一起決定了這兒節點應該放在哪里,我們每一個哈希表節點都有一個next屬性,這個可以解決鏈表沖突的問題,使得多個鍵值一樣的可以連在一起
下面我們看一下哈希表節點的定義:
typedef struct dictEntry { // 鍵 void *key; // 值 union { void *val; uint64_t u64; int64_t s64; } v; // 指向下個哈希表節點,形成鏈表 struct dictEntry *next; } dictEntry;
下面是字典的定義:
type主要是針對不同的類型,private是針對函數的參數
其中計算哈希值的函數就在type里面指向的
有個哈希表數組,ht[1]只有rehash的時候使用,rehashindex也是rehash的時候使用
typedef struct dict { // 類型特定函數 dictType *type; // 私有數據 void *privdata; // 哈希表 dictht ht[2]; // rehash 索引 // 當 rehash 不在進行時,值為 -1 int rehashidx; /* rehashing not in progress if rehashidx == -1 */ } 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;
當加入一個鍵值的時候,我們先根據type里面的函數計算出哈希值,&mask計算出索引值,加入哈希表的指定索引中,
為了解決哈希表的沖突,我們使用拉鏈發,但是為了考慮效率,我們通常將新加入的節點放在最前面,不yongO(N)摻入
rehash:
哈希表的鍵值會不聽的增多減少,為了讓負載因子,維持在一個合理的范圍,我們需要適當的進行擴展和收縮
- 為字典的
ht[1]
哈希表分配空間, 這個哈希表的空間大小取決於要執行的操作, 以及ht[0]
當前包含的鍵值對數量 (也即是ht[0].used
屬性的值): -
- 如果執行的是擴展操作, 那么
ht[1]
的大小為第一個大於等於ht[0].used * 2
的 2^n (2
的n
次方冪); - 如果執行的是收縮操作, 那么
ht[1]
的大小為第一個大於等於ht[0].used
的 2^n 2
- 如果執行的是擴展操作, 那么
- 將保存在
ht[0]
中的所有鍵值對 rehash 到ht[1]
上面: rehash 指的是重新計算鍵的哈希值和索引值, 然后將鍵值對放置到ht[1]
哈希表的指定位置上。 - 當
ht[0]
包含的所有鍵值對都遷移到了ht[1]
之后 (ht[0]
變為空表), 釋放ht[0]
, 將ht[1]
設置為ht[0]
, 並在ht[1]
新創建一個空白哈希表, 為下一次 rehash 做准備。
哈希表的擴展和收縮的條件:
1:如果沒有執行BSAVE或者BGREWRITEAOF,並且負載因子大於等於1
2:如果執行BSAVE或者BGREWRITEAOF,並且負載因子大於等於5
這樣設計是因為如果執行的話,會fork出新的進程,因為遵循寫實復制,為了盡量避免寫入內存進行復制,所以將負載因子提高一些
如果負載因子小0.1執行收縮
漸進事rehash:
因為哈希表的數據可能特別的多,所有rehash不是一次完成的,是多次分批完成的,這里就用到了reashindex,最開始rehashindex=0,表示對索引值0指向的復制,結束了,開始索引值1的,rehashindx+1,這個過程中如果查找的話,會先查找ht[0]->ht[1],添加的話都會添加大1里面,這樣可能保證服務器正常的運作