字典概念
键值对:键和值进行关联,这些关联的键和值就是键值对
字典:又称符号表、关联数组、映射,是一种用于保存键值对的抽象数据结构
哈希表节点定义
typedef struct dictEntry {
void *key;//键
union{
void *val;
uint64 _tu64;
int64 _ts64;
}v;//值
struct dictEntry *next;//指向下个哈希表节点,形成链表,将多个哈希值相同的键值对连接在一起,用于解决键冲突问题
}dictEntry;
哈希表定义
typedef struct dictht{
dictEntry **table;//哈希表数组
unsigned long size;//哈希表大小
unsigned long sizemask;//哈希表大小掩码,用于计算索引值,总是等于size-1
unsigned long used;//该哈希表已有节点的数量
}dictht;
字典定义
//类型特定函数定义
typedef struct dictType{
unsigned int (*hashFunction)(const void *key);//计算哈希值函数(如 MurmurHash算法)
void *(*keyDup)(void *privdata, const void *key);//复制键函数
void *(*valDup)(void *privdata, const void *obj);//复制值函数
void *(*keyCompare)(void *privdata, const void *key1, const void *key1);//对比键函数
void (*keyDestructor)(void *privdata, void *key);//销毁键函数
void (*valDestructor)(void *privdata, void *obj);//销毁值函数
}dictType;
//字典定义
typedef struct dict{
dictType *type;//类型特定函数,为用途不同字典设置不同的类型特定函数
void *privdata;//私有数据,传给那些类型特定函数的可选参数
dictht ht[2];//哈希表 ht[1]用于rehash
int trehashidx;//rehash索引,记录哈希进度,-1 未进行rehash中
}dict;
rehash(重新散列)
为了让哈希表的负载因子维持在一个合理的范围内,当哈希表保存的键值对数量大多或者太少时,需要对哈希表的大小进行相应的扩展或收缩。
Redis对字典的哈希表执行rehash的步骤:
1、为字典ht[1]哈希表分配空间,这个哈希表的空间大小取决于要执行的操作以及ht[0]当前包含的键值对数量(ht[0].used),如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used*2的n次幂,如果执行的是收缩操作,那么ht[1]的大小为第一个大于等于ht[0].used的2的n次幂。
2、将保存在ht[0]中的所有键值对rehash到ht[1]上面rehash指的是重新计算键的哈希值和索引值,然后将键值对放置到ht[1]哈希表的指定位置上。
3、当ht[0]包含的所有键值对都迁移到了ht[1]之后释放ht[0]将ht[1]设置为ht[0],并在ht[1]新创键一个空白哈希表为下一次rehash做准备。
负载因子计算公式:负载因子 = 哈希表节点数量 / 哈希表大小
哈希表的扩展操作:
1、服务器目前没有在执行bgsave命令或者bgrewriteaof命令,并且哈希表的负载因子大于等于1;
2、服务器目前正在执行bgsave命令或者bgrewriteaof命令并且哈希表的负载因子大于等于5;
哈希表的收缩操作:哈希表负载因子小于0.1
哈希表渐进式rehash采用分而置之的方式,避免了集中式rehash带来的庞大计算量,在进行期间,字典的删除、查找、更新等操作会在两个哈希表上进行,新添加到字典的键值对一律会保存到ht[1]里面,则ht[0]不再进行任何添加操作。