内部实现
1 | typedef struct dictEntry { |
结构示意图:

原理讲解
字段
ht 字段
ht 是一个两个项的数组,其中 ht[0] 用于存放哈希表,ht[1] 在哈希重排时使用
rehashidx 字段
当 rehashidx == -1 时,表示当前不是在 rehash, 用于表示在进行 rehash 操作时,进行到了哪一步。
如何操作数据
假设现在需要添加一个数据 <k, v>, 先需要计算哈希值:
1 | hash = dict->type->hashFunction(k); |
然后根据 sizemask 求出索引值:
1 | index = hash & dict->ht[x]->sizemask; |
这样就可以将 <k, v> 存储在 dict->ht[x]->table[index] 中, 如果 table[index] 中已经有数据, 则新添加的数据放在链表表头. (这是因为 table[index] 中的链表时一个单链表,没有指向链表尾部的指针,添加到表头更快)
rehash 过程
当哈希表保存的数据太多或太少时,需要对哈希表进行相应的扩容或者收缩。
如果进行扩容操作,那么 ht[1] 的大小为第一个不小于 ht[0].used * 2 的 2^n (n 为正整数), 如: used = 5, ht[0].used * 2 = 10 < 2^4 = 16, 所以 ht[1] 的大小为:16 ·
然后就可以将 ht[0] 的数据哈希到 ht[1] 中, 当 ht[0] 中的数据全部哈希到 ht[1] 后, 释放 ht[0], 将 ht[1] 变 ht[0]
扩展的触发条件
- 在没有执行
BGSAVE或BGREWRITEAOF时,哈希表的负载因子>= 1 - 在执行
BGSAVE或BGREWRITEAOF时,哈希表的负载因子>= 5
负载因子的计算:
1 | load_factor = ht[0].used / ht[0].size |
在进行
BGSAVE或BGREWRITEAOF时,提高负载因子是为了避免扩容,避免不必要的内存写入
收缩的触发条件
负载因子 < 0.1
渐进式哈希
在扩容或者收缩时,需要将 ht[0] 全部哈希到 ht[1], 如果一次性哈希, 当数据足够多时,会存在效率问题。因此 redis 通过逐步哈希的方式,其具体过程如下:
- 为
ht[1]分配空间 - 设置
rehashidx = 0 - 新添加的数据不再加入到
ht[0], 而是加入到ht[1]中,保证ht[0]不会增加数据 - 将
ht[0]->table[rehashidx]的数据全部哈希到ht[1]中 rehashidx++- 随着不断的执行,
ht[0]的数据哈希到了ht[1], 这是可以 设置rehashidx = 1, 表明rehash结束,释放ht[0],ht[1]设置为ht[0]
在 rehash 的过程中, 删除,查找,更新会同时在 ht[0], ht[1] 中进行, 但是添加只会在 ht[1] 中。