《Redis 设计与实践》读书笔记系列五:字典 rehash
rehash
上一章节有提到,dict
的ht
是一个数组,仅有两个dictht
,内容介绍了dictht
的组成和关系。接下来的内容解释了为什么要有两个dictht
,原因可以总结为因为rehash
。
为什么要有 rehash
随着操作的不断执行,哈希表 ( dictht )
中保存的键值对会增多或减少,如果过多或过少,负载因子就会超出合理的范围。为了让哈希表的负载因子维持在一个合理的范围,就需要对哈希表进行rehash
,达到根据哈希表的大小进行相应的扩展或收缩。
dict
字典中有dictht
属性,是一个数组,ht[1]
则是用于rehash
的。
未进行rehash
时dict
的结构图
rehash
扩容的过程简述
- 将
ht[1]
的table
新建一个dictEntry
数组(扩容必然大于ht[0]
中table
数组的容量)。 - 将
ht[0]
中的键值对重新以ht[1]
中的属性计算哈希和索引值,映射到ht[1].table.dictEntry
中的哈希节点中,并将ht[0]
中dictEntry
数组索引指向的dictEntry
设置为Null
。 - 释放
ht[0]
,并将ht[1]
设置为ht[0]
,然后为ht[1]
为配一个空的哈希表(即table
指向NULL
)。
ht[1]
的扩容算法:
x = ht[0].used * 2
。n = 2 的多少次方(while2的次方不断累加)
。
在n
不断的计算过程当中,当n
第一次大于等于x
时。n
即为ht[1]
的容量。例:
当ht[0].used
等于4
时。x = 8
。n
的幂不断 + 1
,导致n
不断变大,幂等于2
时,n
等于4
。n
未大于等x
。幂继续+1
,等于3
,n
这时等于8
,大于等于x
。这时取8
作为ht[1]
的size
。
ht[1]
的收缩算法:
同扩容算法大概一致。不同的地方在于x
的计算。收缩算法中ht[0].used=x
。即不用乘以2
。
扩容第二步结果图
由上一图作为 rehash 基础作为演示:
rehash
触发条件
负载因子计算方式:load_factor = ht[0].used / ht[0].size
收缩触发条件:负载因子小于0.1
时。
扩容触发条件:
- 当前没有执行
bgsave
或bgrewriteaof
命令,且哈希表负载因子>=1
。 - 当前正在执行
bgsave
或bgrewriteaof
命令,且哈希表负载因子>=5
。
渐进式的rehash
rehash
为什么是渐进式的?
如果哈希表的数据量很大,rehash
的占用会很高,涉及到哈希计算和索引计算,数据转移等。所以rehash
需要分多次,渐进式的完成。
它是如何进行的?
它将rehas
h的操作平摊到每一次对该字典的增删改查的操作上,即每次进行增删改查,都会进行一部分的rehash
操作,并将rehasidx
修改。这就是渐进式,避免了集中式rehash
带来的庞大计算量。
上一图可见,rehashidx
值为3,表明了正在进行rehash
。那为什么是3
而不是2
,rehashidx
还代表着什么?
rehashidx代表ht[0].table这个数组中的索引,意味着当前rehash已经进行到了哪个索引了。3则意为着索引为3的键值对已转移完毕。
当键值对全部转移完毕后。rehashidx
会重新归于-1
。
当rehashidx不等于-1时,这个时候查询一个键值对,会怎样?
不等于-1
,则意味着正在进行rehash
,会先在ht[0]
中查找,没找到就会在ht[1]
中查找。
当进行rehash
时,新增一个键值对会怎样?
会直接新增到ht[1]
中。
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: