Redis设计与实现 (三): 字典

 哈希表

结构定义dict.h/dictht   

/*
* 哈希表
*
* 每个字典都使用两个哈希表,从而实现渐进式 rehash 。
*/
typedef struct dictht {

 // 哈希表数组
 dictEntry **table;

 // 哈希表大小
 unsigned long size;

 // 哈希表大小掩码,用于计算索引值
 // 总是等于 size - 1
 unsigned long sizemask;

 // 该哈希表已有节点的数量
 unsigned long used;

} dictht;

table是一个数组, 每个元素是一个指向dict.h/dictEntry 结构的指针.

哈希表节点

/*哈希表节点*/
typedef struct dictEntry {
    //
    void *key;
    //
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;
    // 指向下个哈希表节点,形成链表
    struct dictEntry *next;

} dictEntry;

next属性指向下一个节点, 形成链表, 解决哈希冲突.

字典

/*
 * 字典
 */
typedef struct dict {
    // 类型特定函数
    dictType *type;
    // 私有数据
    void *privdata;
    // 哈希表
    dictht ht[2];
    // rehash 索引
    // 当 rehash 不在进行时,值为 -1
    int rehashidx; /* rehashing not in progress if rehashidx == -1 */
    // 目前正在运行的安全迭代器的数量
    int iterators; /* number of iterators currently running */
} dict;

type 和 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, void *key);
 // 销毁值的函数
 void (*valDestructor)(void *privdata, void *obj);
} dictType;

ht数组包含两个哈希表,  一般情况下操作ht[0],  ht[1] 只有在ht[0]哈希表进行rehash时使用.  rehashidx, 记录了rehash的进度, 没有则为 -1 ;

哈希算法

Redis 使用MurmurHash2算法计算哈希值

解决键冲突

链地址法,  采用头插法,  时间复杂度为O(1).

rehash

1. 为 ht[1] 分配空间 : 如果是扩容,  分配大小为 第一个大于等于 ht[0] .used * 2 的 2的n次幂; 如果是收缩,  分配空间大小为  第一个大于等于 ht[0] .used 的 2的n次幂.

2.  将ht[0] 上的键值对 通过重新计算键的哈希值和索引重新分配到ht[1] 上.

3. 这时ht[0] 将变为空表,  释放ht[0],  将ht[1] 设置为ht[0],  并在ht[1]创建一个空表, 为下次rehash做准备.

渐进式rehash

在字典维持一个索引计数器 rehashidx, 设置为0, 表示正式工作.

在对字典执行添加, 删除, 查找或修改时,  将 rehashidx 索引上的所有键值对 rehash 到 hd[1], 完成后 rehashidx++;

当ht[0] 上的所有键值对被 rehash到 ht[ 1 ], 设置 rehashidx = - 1, 表示操作已经完成了.

在此期间操作数据, 会使用ht[0] 和 ht[1]两个hash表,  例如要找某个键值, 会先在ht[0] 找, 没有就去ht[ 1 ] 找.  新增操作都保存到ht[ 1 ] 中. 保证了ht[0] 只减不增, 最终为空.

原文地址:https://www.cnblogs.com/tanxing/p/6715002.html