redis字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对
哈希表
哈希表的结构
typedef struct dictht {
//哈希表数组
dictEntry **table;
//哈希表大小
unsigned long size;
//哈希表大小掩码,用于计算索引值,总等于size-1
unsigned long sizemask;
//哈希表以有节点的数量
unsigned long used;
}dictht;
table属性是一个数组,数组中的每一个元素都是一个指向dictEntry的指针,每个dictEntry都保存着一个键值对
size属性记录了哈希表的大小,也就是table数组的大小
sizemask属性的值总等于size-1,这个属性和key的哈希值一起决定一个键将被放到table数组的哪一个索引上
used属性记录了哈希表中已有的节点数量
哈希表节点的结构
typedef struct dictEntry {
//键
void *key;
//值
union {
void *val;
uint_64tu64;
int_64ts64;
}v;
//指向下一个哈希表节点形成链表
struct dictEntry *next;
}dictEntry;
key属性记录了键值对的键值
v属性记录键值对的值,可以是一个指针,一个uint64的整数或者int64的整数
next指向另一个哈希表节点的指针,以此来形成链表解决键哈希冲突的问题
字典
字典的结构
typedef struct dict {
//类型特定函数
dictType *type;
//私有数据
void *privdata;
//哈希表
dictht ht[2];
//rehash索引,当rehash不在进行时值为-1
int rehashidx;
}dict;
type和privdata属性是针对不同类型的键值对,为创建多态字典而设置的,type是一个指向dictType的指针,每个dictType结构保存了一个用于操作特定类型键值对的函数
而privdata属性则保存了需要传给那些特定类型函数的参数
typedef struct dictType {
//计算哈希值的函数
unsigned int (*hashFunction)(const void *key);
//复制键的函数
void *(*keyDup)(void privdata,const void *key);
//复制值的函数
void *(*valDup)(void privdata,const void *obj);
//对比键的函数
int (*keyCompare)(void privdata,const void *key1,const void *key2);
//销毁键的函数
void (*keyDestructor)(void privdata,const void *key);
//销毁值的函数
void (*valDestructor)(void privdata,const void *obj);
}dictType;
ht属性是一个包含两个项的数组,数组中的每个项都是一个dictht哈希表,一般情况下,字典只使用ht[0]哈希表,ht[1]哈希表只会在对ht[0]进行rehash时使用。
rehashidx属性记录了目前rehash的进度,如果目前没有进行rehash,那么她的值为-1。
哈希算法
redis计算哈希值和索引值的方法如下:
hash = dict->type->hashFunction(key);
根据情况不同,ht[x]可以是ht[0]或者ht[1]
index = hash & dict->ht[x].sizemask;
哈希冲突
当两个或两个以上的键被分配到哈希数组的同一个索引上面时,这些键就发生了冲突。
redis使用链地址法来解决冲突,程序总是将新节点添加到表头的位置
rehash
为了让哈希表的负载因子维持在一个合理的范围内,当哈希表的节点数太多或者太少时,程序将对哈希表进行扩展和收缩
当哈希表的负载因子小于0.1时程序会对哈希表进行收缩操作
负载因子 = 哈希表已保存的节点数量 / 哈希表大小
load_factor = ht[0].use / ht[0].size
当hash表满足一下条件的任意一个时,程序会自动对哈希表进行扩展操作:
- 服务器目前没有在执行BGSAVE命令或者是BGREWRITEAOF命令,并且哈希表负载因子大于等于1
- 哈希表负载因子大于等于5
渐进式rehash
rehash的动作不是一次性、集中式的完成的,因为当hash表很大时,一次性的rehash可能会让服务器停止工作一段时间,所以rehash是分多次、渐进式的将ht[0]上的数据rehash到ht[1]上的。
在此期间字典的删除、查找、更新等操作会在两个哈希表上进行,另外新的键值会被保存到ht[1]上,而ht[0]不会在进行任何添加操作,保证ht[0]上的数据只会减少不会增加