哈希表
typedef struct dictht {
//哈希表数组
dictEntry **table;
//哈希表大小
unsigned long size;
//哈希表大小掩码,用于计算索引值
//总是等于size-1,用hash值&掩码得到
unsigned long sizemark;
//该哈希表已有节点的数量
unsigned long used;
}dictht;
哈希表节点
typedef struct dictEntry {
//键
void *key;
//值
union{
void *val;
uint64_t u64;
int64_t s64;
}v;
//指向下一个哈希表节点,形成链表
struct dictEntry *next;
}dictEntry;
typedef struct dict {
//类型特定函数
dictType *type;
//私有数据
void *privdata;
//哈希表
dictht ht[2];
//rehash索引
//当rehash不在进行时,值为-1
int trehashidx;
}dict;
type属性和privdata属性是针对不同类型的键值对,为创建多态字典而设置的。
字典采用的是MurmurHash2算法进行hash操作。算法优势:即使输入的键是有规律的,算法仍能给出一个很好的随机分布性,并且算法的计算速度也非常快。
当发生hash冲突时会用开链的方式。
rehash是为了让hash表的负载因子(used/size)维持在一个合理的范围内,对哈希表进行扩展或收缩,此时用到ht[1]。
如果是扩展操作,则将ht[1]的大小设为第一个大于等于ht[0].used*2的2的ht[0].used*2的2^n
如果是收缩操作,那么ht[1]的大小应该为第一个大于等于ht[0].used的2^n。
将ht[0]的键值对重新进行hash操作放入ht[1]中。然后将ht[1]作为ht[0],将ht[1]置空。
当以下条件中任意一个被满足时,程序自动对hash表进行扩展操作:
1.服务器目前没有执行BGSAVE命令或者BGREWRITEAOF命令,并且hash表的负载因子大于等于1.
2.服务器正在执行BGSAVE命令或者BGREWRITEAOF命令,并且hash表的负载因子大于等于5.
为什么要这样,因为在执行这两个命令时,Redis要创建当前服务器的子进程,而大多操作系统采用写时复制的方法优化子进程的使用效率,所以在子进程存在期间,服务器
会提高负载因子,从而尽可能的避免在子进程存在期间进行hash表的扩展操作,这可以避免不必要的内存写入操作,最大限度节约内存,
当hash表的负载因子小于0.1时会自动进行收缩操作。
渐进式rehash
用到了rehashidx,而不是一次性的将ht[0]中的所有键值对rehash到ht[1]。也就是将rehshidx不断加一,一行一行将其键值对搞到ht[1].
这样的好处是,例如此时有个删除操作我就只需要对比reshidx的值,从而判断需不需要在ht[0]里找。这样就能减轻负担,如果没有rehashidx,就要一直找,无法判断是否在ht[0]中。
对于正在rehash时添加操作会直接在ht[1]操作,其他删除,查找,更新,都要在ht[0]找完ht[1]找。