Redis数据结构-字典
字典
字典,又称符号表(symbol table)、关联数组(associative array)或映射(map),是一种用于保存键值对(key-value pair)的抽象数据结构。
字典实现
Redis的字典使用哈西表作为底层实现,一个哈希表里面可以由多个哈西表节点,而每个哈希表节点就保存了字典中的一个键值对
哈希表
Redis字典所使用的哈希表由dict.h/dictht结构定义
1 | /* |
table属性是一个数组,数组中的每一个元素都是一个指向dict.h/dictEntry结构的指针,每个dictEntry结构保存着一个键值对。size属性记录了哈希表的大小,也是table数组的大小,而used属性记录了哈希表目前已有节点(键值对)的数量。sizemask属性的值总是等于size-1,这个属性和哈希值一起决定一个键应该被放到table数组的哪个索引上。
图中为一个空的哈希表,没有包含任何键值对
哈希表节点
哈希表节点使用dictEntry结构表示,每个dictEntry结构都保存着一个键值对:
1 | /* |
key属性保存着键值对中的键,而v保存键值对中的值,其中键值对的值可以是一个指针,或者一个uint64_t整数,又或者是个int64_t整数。
next属性是指向是指向另一个哈希表节点的指针,这个指针可以将多个哈希值相同的键值对连接在一起,以此来解决键冲突(collision)问题
字典
Redis中的字典由dict.h/dict结构表示
1 | /* |
type属性和privdate属性是针对不同类型的键值对,为创建多态字典而设置的:
- type属性是一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特性类型键值对的函数,redis会为用途不同的字典设置不同类型特性函数
- 而privdata属性则保存了需要传给那些类型特定函数的可选参数
1 | /* |
ht属性是一个包含两个项的数组,数组中每个项都是一个dictht哈续表,一般情况下,字典只使用h[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用。
除了ht[1]之外,另一个和rehash有关的属性就是rehashidx,它记录了reash目前的进度,如果目前没有在进行rehash,那么它的值是-1。
哈希算法
Redis计算hash值和索引值的方法如下:
1 | // 计算给定键的哈希值 |
如,讲一个键值对k0和v0添加到字典里面,则会先使用hash = dict→type→hashFunction(k0)计算键k0的哈希值。假设计算出来的哈希值为8,则会继续使用 index = hash & dict->h[0].sizemask = 8 & 3 = 0 计算出键k0的索引值0,这表示包含键值对k0和v0的节点应该被放置到哈希表数组的索引0位置上
解决键冲突
Redis的哈希表使用链地址法(separate chaining)来解决键冲突,每个哈希表节点都有一个next指针,多个哈希表可以组成一个单向链表,被分配到同一个索引上的多个节点 可以用这个单向链表连接起来,这样就解决的键冲突问题
Rehash
为了让哈希表的负载因子(load factor)维持在一个合理的范围内,当哈希表保存的键值对数量过多或过少时,程序需要对哈希表的大小进行相应的扩展或者收缩
Rehash步骤如下:
- 为字典ht[1]哈希表分配空间,这个哈希表的空间大小取决于要执行的操作,以ht[0]当前包含的键值对数量(h[0].used属性的值)。如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used*2的2^n;如果是收缩操作,那么ht[1]的大小为第一个大于等于ht[0].used的2^n
- 讲保存在ht[0]中的所有键值对rehash到ht[1]上面;rehash指的是重新计算哈希和索引值,然后讲键值对放置到h[1]哈希表的指定位置上
- 当h[0]包含的所有键值对都迁移到ht[1]上之后,释放h[0],将ht[1]设置成h[0],并在ht[1]新创建一个空白哈希表,为下一次rehash做准备
哈希表的扩展与收缩
以下条件任意一个被满足时,程序自动开始对哈希表执行扩展操作:
- 服务器目前没有在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于1
- 服务器目前正在 执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于5
其中哈希表的负载因子可以通过
1 | # 负载因子 = 哈希表已保存节点数量 / 哈希表大小 |
另一方面,当哈希表的负载因子小于0.1时,程序自动开始对哈希表执行收缩操作
渐进式rehash
渐进式rehash的步骤:
- 为ht[1]分配空间,让字典同时持有ht[0]和ht[1]两个哈希表
- 在字典中维持一个索引计数变量rehashidx,并将它的值设置为0,表示rehash工作正式开始
- 在rehash进行期间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定的操作以外,还会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1],当rehash工作完成之后,程序将rehashidx属性的值增1
- 随着字典操作的不断执行,最终在某个时间点上,ht[0]的所有键值都会rehash到ht[1],这时程序将rehashidx属性的值设为-1,表示rehash操作已完成
渐进式rehash执行期间的哈希表操作
因为渐进式rehash过程中,字典会同时使用ht[0]和ht[1]两个哈希表,所以在这期间字典删除、查找。更新等操作,会在两个哈希表执行





