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

 

 redis设计与实现总结--字典_键值对_03
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]找。