字典,用于保存键值对的抽象数据结构。
每个键都是独一无二的。
Redis的数据库就是使用字典作为底层实现的。
还是哈希键的底层实现之一,当一个hash键包含的键值对比较多,又或者键值对中的元素都是比较长的字符串时,Redis就用字典作为哈希键的底层实现
哈希表 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]找。