Redis設計與實現-04-字典

《Redis設計與實現》黃建宏版的讀書筆記

哈希表

  • 哈希表(hash table):又叫散列表,是根據關鍵碼值進行訪問的數據結構。將關鍵碼值映射到表中的一個位置來訪問,以加快查找的速度。這個函數映射叫做哈希函數,存放記錄的數組叫做散列表。
  • 哈希表常用於通過key快速的找到對應的value時使用。
  • 哈希表的負載因子等於實際元素數目/哈希表的容量,負載因子越大表示衝突越大,負載因子越小,表示空間越浪費。一般負載因子位於0-0.7。
  • 哈希算法:當將一個新的鍵值對添加到字典裏面時,先根據鍵值對的鍵計算出哈希值和索引值,然後根據索引值,將包含新鍵值對的哈希表節點放到哈希表數組的指定索引上面。
  • 鍵衝突:當有兩個或以上數量的鍵被分配到了哈希表數組的同一個索引上時,就稱爲鍵衝突,Redis使用的是鏈地址法來解決衝突。dictht中沒有鏈表表尾的指針,爲了速度考慮,Redis將新增加的節點放到鏈表的表頭位置。
  • 爲了讓哈希表的負載因子維持在一個合理的範圍內,當哈希表保存的鍵值對數量太多或者太少時,程序需要對哈希表的大小進行相應的擴展或者收縮(通過rehash完成)。
  • Redis執行rehash的步驟如下:
    1. 爲ht[1]哈希表分配空間,空間大小取決於要執行的操作和ht[0]當前包含的鍵值對。
      • 如果是擴展操作,ht[1]的大小爲第一個大於等於ht[0].used * 2 的 2的n次方。
      • 如果是收縮操作,ht[1]的大小爲第一個大於等於ht[0].used 的2的n次方。
    2. 將保存在ht[0]中的所有鍵值對rehash到ht[1]上面。
    3. 當ht[0]包含的所有鍵值對都遷移到ht[1]之後,釋放ht[0],將ht[1]設置爲ht[0],並將ht[1]新創建一個空白哈希表。
  • hash 表擴展的條件,BGSAVE、BGREWRITEAOF命令會產生新的子進程,大多數操作系統都是寫時複製,爲了避免在子進程期間進行哈希表的擴展操作節省內存,Redis會提高此時擴展操作所需要的負載因子。
    1. 服務器沒有執行BGSAVE、BGREWRITEAOF命令時,且哈希表的負載因子大於等於1。
    2. 服務器執行BGSAVE、BGREWRITEAOF命令時,且哈希表的負載因子大於等於5。
  • hash 收縮操作的條件:負載因子小於0.1時。
  • rehash動作並不是一次性完成的,而是漸進式的,詳細步驟如下:
    1. 爲ht[1]分配空間
    2. 維護一個索引計數器變量rehashidx,並將它的值設置爲0
    3. 當字典執行添加、刪除、查找或更新操作時,程序除了執行指定的操作外,還會順帶將ht[0]哈希表在rehashindx索引上的所有鍵值對rehash到ht[1],當完成後,程序將rehashidx屬性的值增一
    4. 當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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章