Redis 字典

字典,又稱爲符號表(symbol table)、關聯數組(associative array)或映射(map),是一種保存鍵值對的數據結構。Redis中數據庫就是用字典做爲底層實現。算法

 

Redis字典所使用的哈希表源碼

dict.h/dictht源碼:數據庫

typedef struct dictht {

  //哈希表數組
  dictEntry **table;

  //哈希表大小
  unsigned long size;

  //哈希表大小掩碼,用於計算索引值,老是等於size - 1
  unsigned long sizemask;

  //已有的節點數
  unsigned long used;

} dictht;

dict.h/dict源代碼:數組

typedef struct dict {

  //類型特定函數
  dictType *type;

  //私有數據
  void *privdata;

  //哈希表
  dictht ht[2];

  //rehash索引,當沒有進行rehash時爲-1,大於-1時表示準備處理dictEntry數組下標的值
  int rehashidx;

  //目前正在運行的安全迭代器的數量
  int iterators;

} dict;

dict.h/dictEntry源代碼:安全

typedef struct dictEntry {

  void *key;

  union {

    void *val;

    unit64_t u64;

    int64_t s64;

  } v;

  //指向下個哈希表節點
  struct dictEntry *next;

} dictEntry;

dict.h/dictType源代碼:服務器

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;

一個字典結構以下:數據結構

字典實現和Java的HashMap相似,都是由一個Entry數組,數組中放Entry鏈表方式實現,當添加一個元素時,先根據元素key算出應該放入數組的下標值,添加Entry到鏈表最後。函數

index算法:優化

#計算key的哈希值spa

hash = dict ->type->hashFunction(key);操作系統

#使用哈希表的sizemask屬性和哈希值,計算出索引值,x多是0或1根據是否在rehash中肯定

index = hash & dict ->ht[x].sizemask;

 

rehash

字典會在兩種狀況下觸發rehash擴充哈希表數組長度:

  • 當服務器當前沒有執行BGSAVE命令或者BGREWRITEAOF命令時,哈希表的負載因子大於1
  • 當服務器當前正在執行BGSAVE命令或者BGREWRITEAOF命令時,哈希表的負載因子大於5

哈希表收縮條件

  • 當哈希表的負載因子小於0.1時,程序自動開始對哈希表進行收縮操做。

#負載因子 = 哈希以保存節點數 / 哈希表數組長度

load_factor = ht[0].used / ht[0].size

之因此當服務器在執行BGSAVE和BGREWRITEAOF時提升了負載因子儘可能在這個時候進行rehash是由於這個兩個操做時Reids須要建立當前服務器進程的子進程,而大多數操做系統都會採用寫時複製(copy-on-write)技術來優化子進程效率,若是在這期間進行rehash寫操做作會須要額外複製到子進程,致使內存使用加大。

 

字典的rehash過程:

  1. 爲字典的ht[1]哈希表分配空間,擴展操做空間大小默認爲>=ht[0].used*2而且與其最接近的2^n值;收縮空間大小默認爲>=ht[0].used而且與其最接近的2^n值;
  2. 將ht[0]中的全部dictEntry從新計算索引放入ht[1],爲了保證rehash不會致使每次操做等待時間過長,這個過程是漸進的,每次只須要保證移動一個dictEntry鏈表,rehashidx就是爲了記錄當前該移動的dictEntry下標存在的,當開始移動時,rehashidx會先被設置爲0,而後移動下標爲0的dictEntry鏈表,完成後rehashidx=1,如此重複。
  3. 當全部dictEntry移動完成後,將rehashidx = -1,ht[0] = ht[1],ht[1]置爲空哈希表

 

在rehash沒有完成期間,字典全部的delete、find、update都會先在ht[0]操做若是沒有找到再去ht[1]進行,instert操做會直接在ht[1]進行,這樣保證了ht[0]的鍵值對數量只減不增。

相關文章
相關標籤/搜索