字典介绍
字典,又称为符号表、关联数组或映射,是一种用于保存键值对的抽象数据结构,
Redis使用的C语言并没有内置这种数据结构,因此自己构建字典的实现。
Redis对数据库的增删改查也是构建在字典的操作之上的。
redis> SET msg "hello workd"
//在数据库中创建
//键为"msg" 值为"hello world"的键值对
字典只是 哈希键 的底层实现之一。
当一个哈希键包含的键值对比较多或键值对中元素都是比较长的字符串,Redis使用字典作为哈希键的底层实现。
字典的实现
一个哈希表里可以有多个哈希表节点,而每个哈希表节点保存了字典中的一个键值对。
哈希表
typedef struct dictht{
dicEntry **table;//哈希表数组
unsigned long size;//哈希表大小
unsigned long sizemask;//哈希表掩码,计算索引值,总等于size-1
unsigned long used;//哈希表已有节点的数量
}dictht;
//table是一个数组,每个元素指向dictEntry结构的指针,每个dictEntry结构保存着一个键值对,table是一个hash表
//size属性记录哈希表大小,也就是table的大小。
哈希表节点
哈希表节点使用dictEntry结构标识,每个dictEntry结构都保存着一个键值对:
typedef struct dictEntry{
void *key;//键
union{
void *val;
uint64_tu64;
int64_ts64;
}v;//值
struct dictEntry *next;//指向下个哈希表节点
// 解决hash冲突使用 链地址法
}dictEntry;
字典
typedef struct dict{
dictType *type;//类型特定的函数
void *privdata;//私有数据
dictht ht[2];//哈希表
int trehashidx;//索引
}dict;
//ht每一个都是一个哈希表
//一般情况字典只使用ht[0]哈希表 ht[1]在对ht[0]进行rehash时使用
//type是保存一簇用于操作特定类型键值对的函数
typedef struct dictType{
//计算哈希值函数
unsigned int(*hashFunction)(const void *key);
//复制键函数
void *(*keyDup)(void *privdata,const void *key);
...
}dictType;
哈希算法
当将一个新的键值对添加到字典里面是,要先根据键计算出哈希值和索引值,然后再根据索引值,将包含新键值对的哈希表节点放到哈希表数组的指定索引上面。
//使用字典设置的哈希函数,计算key的哈希值
hash=dict->type->hashFunction(key);
//使用哈希表的sizemask属性和哈希值,计算索引值
index=hash&dict->ht[x].sizemask;
//将一个k0 v0添加到字典里面
hash=dict->type->hashFunction(k0);
//假设hash值为8
index=hash&dict->ht[0].sizemask=8&3=0;
//则放入dictEntry数组下表0位置
解决键冲突
Redis的哈希表使用链地址法来解决键冲突,每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,从而解决键冲突问题。
rehash
为了让哈希表的负载因子维持在一个合理的范围之内,当哈希表保存的键值对数量太多或者太少时,程序需要对哈希表的大小进行相应的扩展或者收缩。
Redis的hash步骤如下:
1.为字典ht[1]哈希表分配空间
如果执行扩展,ht[1]大小为第一个大于等于ht[0]*2的2的n次方幂
如果执行收缩,ht[1]大小为第一个大于等于ht[0].used的2d的n次方
例如 ht[0].used当前值为4,4*2=8 是2的3次方
2.将保存在ht[0]中的所有键值对rehash到ht[1]上面:rehash指的是重新计算键的哈希值和索引值,然后放到ht[1]哈希表上
3.释放ht[0],将ht[1]设置到ht[0]上,并给ht[1]创建一个空白哈希表
渐进式rehash
首先先给ht[1]分配空间,然后循环的对每一个键操作从ht[0]中每个dictEntry放入ht[1]中(rehash过程不是一次性,而是分多次),然后再把ht[1]赋值给ht[0]。