Redis字典
字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键
字典,又称为符号表,关键数组或映射,是一种用于保存键值对的抽象数据结构
字典中,一个键可以和一个值进行关联。键是独一无二的
Redis使用的C语言同样没有内置字典这种数据结构,因此Redis构建了自己的字典实现。
Redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对。
哈希表
dict/dictht结构:
typedef struct dictht{
//哈希表数组
dictEntry **table;
//哈希表大小
unsigned long size;
//哈希表大小掩码,用于计算索引值
//总是等于size-1
unsigned long sizemask;
//该哈希表已有节点的数目
unsigned long used;
}dictht
table属性是一个数组,数组中的每个元素都是一个指向dict.h/dictEntry结构的指针,每个dictEnter结构保存着一个键值对。sizemask属性的值总等于size-1,这个属性和哈希值一起决定一个键应该被放在table数组的哪个索引上面。
哈希表节点
哈希表节点使用dictEntry结构表示,每个dictEntry结构都保存着一个键值对
typedef struct dictEntry{
//键
void *key;
//值
union{
void *val;
uint64_tu64;
int64_ts64;
}v;
//指向下个哈希表节点,形成链表
struct dictEntry *next;
}dictEntry;
字典
字典由dict.h/dict结构表示、
typedef struct dict{
//类型特性函数
dictType *type;
//私有数据
void *privdata;
//哈希表
dictht ht[2];
//rehash索引
//当rehash不在进行时,值为-1
int rehashidx;
}dict;
type是一个指向dictType结构的指针,每个dictType结构都保存了一簇用于操作特定类型键值对的函数,Redis会为用途不同的字典设置不同的类型特定函数
而privdata属性保存了需要传给那些类型特定函数的可选参数
ht中的每个项都是一个dicthe哈希表,一般情况下,字典只使用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用
rehashidx记录了rehash目前的进度
哈希算法
要将一个新的键值添加到字典里面时,程序需要先根据键值对的键子算出哈希值和索引值,然后再根据索引值,将包含新键值对的哈希表节点放到哈希表数组的指定索引上。
先算出hash值(dict->type->hashFunction()),再计算出索引值(hash&dict->ht[0].sizemask)
解决键冲突
当有两个或两个以上数目的键被分配到了哈希表数组的同一个索引上面时,我们称这些键发生了冲突。
使用链地址法来解决键冲突,使用单向链表链接,以解决冲突的问题
rehash
为了让哈希表的负载因子维持在一个合理的范围之内,当哈希表保存的键值太多货币太少时,程序需要对哈希表的大小进行相应的扩展或收缩即rehash
步骤:
(1)为字典中的ht[1]分配空间
(2)将保存再ht[0]中的所有键值对rehash到ht[1]上面。重新计算键的哈希值和索引值
(3)释放ht[0],将ht[1]设为ht[0],并在ht[1]新创建一个空白哈希表
渐进式rehash
操作(2)不是一次性操作的,而是分多次,渐进式的完成的
考虑到数目庞大的情况,需要渐进式的操作
在rehash工作开始后,会改变rehashidx的值
rehashidx索引对应的键值对会被转到ht[1]上,完成rehash后,rehashidx的值—+1
渐进式rehash执行期间,新添加到字典的键值一律会被保存到ht[1]中