redis设计与实现读书笔记四(字典)

字典定义

字典又称符号表、关联数组或者映射,是一种保存键值对的抽象数据结构。一个key可以和一个值进行关联。每一个键都是独一无二的,程序可以通过键来操作值。
Redis的数据库就是使用字典作为底层实现,对数据库的增删改查都是建立在对字典的操作之上。Redis构建了自己的字典实现。
Redis使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,每个哈希表节点就保存了字典中的一个键值对

哈希表

redis字典使用的哈希白哦由dict.h/dictht结构定义

typedef struct dictht {
    dictEntry **table;//哈希表数组 每个元素指向dictEntry结构的指针
    unsigned long size;//哈希表大小(table元素个数)
    unsigned long sizemask;//哈希表大小掩码,计算索引值 等于size-1
    unsigned long used;//哈希表已有节点的数量     
}dictht

table中每一个元素指向dict.h/dictEntry结构,每个dictEntry保存着一个键值对,sizemask属性和哈希值一起决定一个键应该被放到table数组的哪个索引上面。


哈希表节点

哈希表节点使用dictEntry结构表示

typedef struct dictEntry {
    void *key;//键
    union{
        void *val;
        uint64 tu64;
        int64 ts64;
    } v;//值
    struct dictEntry *next;//指向下一个哈希表节点,形成链表
} dictEntry;

next属性指向另一个哈希表节点的指针,可以将多个哈希值相同的键值对链接在一起,解决键冲突的问题。
redis设计与实现读书笔记四(字典)_第1张图片

字典实现

redis字典由dict.h/dict结构定义

typedef struct dict {
    dictType *type;//特定类型函数
    void *privdata;//私有数据
    dictht ht[2];//哈希表
    int rehashidx;//rehash 索引,rehash不在进行时,值为-1
} dict

type属性和privdata属性针对不同类型的键值对,为创建多态字典而设置

  • type属性指向一个dictType结构的指针,每个dictType保存了一簇用于操作特定类型的键值对的函数。redis会为用途不同的字典设置不同的类型特定函数。
  • privdata则保存了要传给这些特定函数的可选参数
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,const *key);
    //销毁值得函数
    void (*valDestructor)(void *privdata,void *obj);
} dictType;

哈希算法

当要讲一个新的键值对添加到字典里面时,先根据键值对的计算出哈希值索引值,再根据索引值将包含新键值对的哈希表节点放到哈希表数组指定的索引上面。计算方法如下(使用**MurmurHash**2算法):
1.使用字典设置的哈希函数,计算出键key的哈希值;
hash = dict->type->hashFunction(key);
2.使用哈希表的sizemask属性和hash值计算出索引值,不同情况可能是ht[X]可能是ht[0]或ht[1]
index = hash & dict->ht[x].sizemask;

解决hash冲突

得到index后就能确定将新键值对(哈希表节点)放到ht[x]的对应位置。如果遇到对应索引上已经有了值,则会发生冲突。redis的哈希表使用链地址法来解决冲突。每一个哈希表节点上的next属性可能将分配到同一索引的哈希节点连接成一个单向链表,即可解决hash冲突。因为dictEntry并没有像链表那样保存一个tail指针,所以为了效率考虑,每次新加入的节点都是添加到链表的头部位置,排在最前面。

rehash

为了维持负载因子在合理范围,字典需要对hash表执行rehash的步骤如下
1. 为字典ht[1]的哈希表分配空间,空间大小取决于要执行的操作和ht[0]当前包含的键值对数量(ht[0].used属性的值)
- 如果执行是扩展操作,那么 ht[1].size >= ht[0].used * 2 的2的n次幂
- 如果是执行收缩操作,那么 ht[1].size >= ht[0].used 的2的n次幂
2. 将保存在ht[0]中所有键值对rehash到ht[1]上面,重新计算键值和索引值,
3. 当ht[0]所有的键值对都迁移完毕,释放ht[0],并将ht[1]设置为ht[0],并在ht[1]新创建一个空白哈希表为下一次rehash做准备。

哈希表的扩展和收缩

当以下条件中的任意一个被满足时,程序会自动开始对哈希表执行扩展操作。
1. 服务器目前没有在执行BGSAVE命令 或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于1
2. 服务器目前正在执行BGSAVE命令或者BGREWRITEAOF 命令,并且哈希表的负载因子大于等于5.

哈希表负载因子计算方式;负载因子 = 哈希表以保存节点数量/哈希表大小
load_factor = ht [0].used / ht[0].size

哈希表在执行BGSAVE命令 或者BGREWRITEAOF命令时,需要创建子进程时,在子进程存在时提升负载因子,避免进行rehash操作,最大限度节约内存。当哈希表负载因子小于 0.1时,hash表会自动进行rehash操作。

你可能感兴趣的:(redis设计与实现,redis,字典实现)