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]的键值对数量只减不增。

相关文章
相关标签/搜索