redis系列6---底层数据结构之字典

251 阅读4分钟

字典是什么?

一种用于保存键值对的抽象数据结构

在redis中有哪些用处?

redis数据库的底层实现(增、删、查、改)
哈希键的底层实现之一

如何实现?

使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对。
//哈希表
typedef struct dictht {
    dictEntry **table;//哈希表数组
    unsigned long size;//哈希表的大小
    unsigned long sizemask;//等于size-1
    unsigned long used;//记录了哈希表目前已有节点的数量
} dictht;
//哈希表节点
typedef struct dictEntry {
    void *key;//键
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;//值
    struct dictEntry *next;//指向另一个哈希表节点的指针
} dictEntry;
//字典
typedef struct dict {
    dictType *type;//一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数
    void *privdata;//保存了需要传给那些类型特定函数的可选参数
    dictht ht[2];//包含两个dictht的数组,只使用ht[0],ht[1]只会在对ht[0]进行rehash时使用
    long rehashidx; //记录了rehash目前的进度,没有进行rehash值为-1
    int iterators; //当前运行迭代器的数量
} dict;
//字典类型
typedef struct dictType {
    unsigned int (*hashFunction)(const void *key);//计算hash值
    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, void *key);//销毁键
    void (*valDestructor)(void *privdata, void *obj);//销毁值
} dictType;

如何将一个新的键值对添加到字典里面?

1、根据键值对的键计算出哈希值(MurmurHash2算法)
    hash = dict->type->hashFunction(key);
2、使用哈希表的sizemask属性和哈希值,计算出索引值,根据情况不同,x可以是0或者1。
    index = hash & dict->ht[x].sizemask;
3、根据索引值将包含新键值对的哈希表节点放到哈希表数组的指定索引上面

如何解决键冲突?

链地址法(因为dictEntry节点组成的链表没有指向链表表尾的指针,所以将新节点添加到链表的表头位置)

什么是哈希表的负载因子?

负载因子 = 哈希表已保存节点数量/哈希表大小

什么时候哈希表会进行扩展?

1、服务器目前没有在执行bgsave命令或者bgrewriteaof命令,并且哈希表的负载因子大于等于1
2、服务器目前正在执行bgsave命令或者bgrewriteaof命令,并且哈希表的负载因子大于等于5

什么时候哈希表会进行收缩?

当哈希表的负载因子小于0.1

如何对ht[1]分配空间?

取决于要执行的操作,以及ht[0]当前包含的键值对数量(used属性)
        如果是扩展操作:ht[1]的大小第一个大于等于ht[0].used*22的n次方幂
        如果是收缩操作:ht[1]的大小第一个大于等于ht[0].used2的n次方幂

如何对哈希表进行扩展或者收缩工作?

rehash(基本步骤)
    1、为ht[1]哈希表分配空间
    2、将保存在ht[0]中的所有键值对放置到ht[1]上面
    3、当ht[0]的所有键值对都迁移到ht[1]之后,释放ht[0],将ht[1]设置为ht[0],并在ht[1]新创建一个空白哈希表,为下一次rehash做准备

渐进式rehash(具体过程)
    1、为ht[1]分配空间,让字典同时持有01两个哈希表
    2、在字典中维持一个索引计数器变量rehashidx,并将它的值设置为0,表示rehash工作正式开始
    3、在rehash进行期间,每次对字典执行增删改查操作时,程序除了执行指定操作外,还会将0哈希表在rehashidx索引上的所有键值对
    rehash到1上,当rehash完成后,程序将rehashidx属性的值+1
    4、随着字典操作的不断执行,完成了从01的rehash,这时rehashidx值设为-1,表示rehash操作完成。

渐进式rehash执行期间的哈希表操作?

字典会同时使用0和1两个哈希表,查找会先从0再到1,插入直接插入到1中

API

dictCreate:创建一个新的字典!
dictAdd:将给定的键值对添加到字典里面
dictReplace:用新值取代旧值
dictFetchValue:返回给定键的值
dictGetRandomKey:从字典中随机返回一个键值对
dictDelete:从字典删除给定键所对应的键值对
dictRelease:释放给定字典,以及字典包含的所有键值对
以上除了dictRelease的时间复杂度是O(N),n为键值对的数量,其余都是O(1)

补充一个图: