Redis中的hash实现。
Redis中hash表的数据结构:
typedef struct dictht{
dictEntry **table; //hash表数组
unsigned long size; //hash表大小
unsigned long sizemask; //hash表大小掩码,等于size-1,和hash值一起计算索引值
unsigned long used; //hash表中元素个数
}
其中hash数组中每一个元素指向一个dictEntry结构(hash表节点)。每个dictEntry都保存着一个键值对和一个dictEntry类型的next指针,这个指针是用来解决键冲突问题的。dictEntry的数据结构如下:
typedet struct dictEntry{
//键
void *key;
//值
union{
void *val;
uint64_t u64;
int64_t s64;
}v;
//指向下一个节点,形成链表
struct dictEntry *next;
}
Redis中的字典使用hash表作为底层的实现,一个hash表里面可以有多个hash表节点,而每个hash表节点就保存的字典中的一个键值对。
字典的数据结构:
typedef struct dict{
dictType *type; //类型特定函数
void *privdate; //私有数据
dictht ht[2]; //hash表
int rehashidx; //rehash索引
}
【注】其中字典中包含两个hash表,一般情况下只使用ht[0]。在rehash时才会用到ht[1]。
hash算法:
当添加一个新的键值对到dict中时,会根据key使用hash函数计算出hash值,再根据sizemask计算出索引,然后将这个新的hashEntry节点使用头插法的方法插入到对应索引的链表中。
rehash
当hash表中的元素超过了某个阈值或者是低于某个阈值时,为了使负载因子处在一个合理的范围内,需要对hash表进行扩容或者收缩:
1.为ht[1]分配合理的空间。
2.将ht[0]中的元素rehash到ht[1]中
3.释放ht[0],将ht[1]赋值给ht[0],将ht[1]置空,为下一次rehash作准备。
【注】
1.rehash这个动作不是一次性完成的,而是渐进式完成的。在每一次查询,插入,更新或删除操作时,会根据dict中的rehashidx的值将ht[0]中对应位置的hash节点rehash到ht[1]中。直到ht[0]中所有的节点都rehash到ht[1]才会释放ht[0],将ht[1]赋值给ht[0],并将ht[1]置空。在rehash的过程中,各个操作都是基于两个hash表进行的,例如查询操作是:先在ht[0]表中查询,查不到再到ht[1]表中查询。插入操作是直接插入到ht[1]中。
2.
- 当负载因子大于等于1(服务器没用执行BGSAVE或者BGREWRITEAOF命令)或者5时,则进行扩展操作,那么ht[1]的大小为不小于ht[0].used*2的第一个2n;
- 当负载因子小于等于0.1时,则进行收缩操作,那么ht[1]的大小为不大于ht[0].used的第一个2n。