• redis数据结构字典


    字典,是一种用于保存键值对的抽象数据结构。在字典中,一个键对应一个值。字典中的每个键都是独一无二的,程序可以再字典中根据键查找与之关联的值,更新值,或者删除整个键值对。字典经常作为一种数据结构在高级语言里,C语言中没有内置这种数据结构,所以Redis构建了自己的字典实现。

    字典的实现

    Redis的字典使用哈希表作为底层实现,一个哈希表里面可以哈希节点,每个哈希节点就保存了字典中的一个键值对

    哈希表

    Redis 所使用的哈希表由 dictht 结构定义

    typedef struct dictht {
        // 哈希表数组,数组中的每个元素都是一个指向 dictEntry 结构的指针,每个 dictEntry 都保存着一个键值对
        dictEntry **table;
        // 哈希表大小,也是哈希表数组的大小
        unsigned long size;
        // 哈希表大小掩码,用于计算索引值。总是等于size-1
        unsigned long sizemask;
        // 该哈希表已有节点数量
        unsigned long used;
    } dictht;

    一个大小为4的空哈希表

    哈希表节点

    哈希表节点使用 dictEntry 结构表示,每个 dictEntry 结构都保存着一个键值对

    typedef struct dictEntry {
        //
        void *key;
        //
        union {
            void *val;
            uint64_t u64;
            int64_t s64;
            double d;
        } v;
        // 指向下一个哈希表节点,形成链表。可以将多个哈希值相同的键值对链接再一起,来解决哈希冲突问题。
        struct dictEntry *next;
    } dictEntry;

    两个哈希值相同的键

     

     字典

    Redis 中的字典由 dict 结构实现

    typedef struct dict {
        // 类型特性函数,指向 dictType 的指针,每个 dictType 结构保存了用于操作特定类型键值对的函数
        dictType *type;
        // 私有数据,保存了需要传给类型特定函数的可选参数
        void *privdata;
        // 哈希表,一般情况下只使用ht[0]哈希表,ht[1]只会在 rehash 时使用
        dictht ht[2];
        // rehash索引,当rehash不在进行时,值为-1
        long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    } dict;
    
    typedef struct dictType {
        // 计算哈希值的函数
        uint64_t (*hashFunction)(const void *key);
        // 复制键的函数
        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;

    普通状态下没有进行rehash的字典

     

    rehash 

    当哈希表保存的键值对数量太多或者太少时,程序需要对哈希表的大小进行相应的扩展或收缩。扩展和收缩哈希表可以通过 rehash 操作来完成。

    哈希表执行 rehash 的步骤

    1. 为字典的 h[1] 哈希表分配空间。如果执行的是扩展操作,ht[1] 的大小为第一个大于等于 ht[0].used * 2 的 2 n,如果执行的是收缩操作,ht[1] 的大小为第一个大于等于 ht[0].used 的 2n。(有点绕,可以这样理解,如果是扩展操作的话判断 21 是否大于等于 ht[0].used*2,如果不成立的话继续判断 22 以此类推,收缩同理)
    2. 将保存在 ht[0] 中的所有键值对 rehash 到 ht[1] 上。
    3. 当 ht[0] 上的所有键值对都 rehash 到 ht[1] 上之后(ht[0] 变为空表),释放 ht[0] 将 ht[1] 设置为 ht[0] ,在 ht[1] 创建一个空白的哈希表为下一次 rehash 做准备。

    哈希表的扩展与收缩

    当以下条件中的任意一个满足时,程序会自动开始对哈希表执行扩展操作:

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

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

    load_factor = ht[0].user / ht[0].size

    当哈希表的负载因子小于0.1时,程序自动开始对哈希表执行收缩操作。

    渐进式的 rehash

    rehash 并不是一次性、集中式完成的,而是分多次、渐进的完成。

    渐进式 rehash 的步骤

    1. 为 ht[1] 分配空间,让字典同时持有 ht[1] 和 ht[0]。
    2. 在字典中维持一个索引计数器变量 rehashidx ,并将他的值设置为0,表示 rehash 工作正式开始。
    3. 在 rehash 进行期间,每次对字典进行操作时,程序还会顺带将 ht[0] 哈希表在 rehashidx 索引上的所有键值对 rehash 到 ht[1],当 rehash 工作完成后,程序将 rehashidx 属性的值增一。
    4. 随着字典操作的不断执行,ht[0] 的所有键值对都会被 rehash 到 ht[1] 上,这时程序将 rehashidx 属性值设置为-1,表示 rehash 操作已完成。

    要在进行渐进式 rehash 的过程中查找字典的一个键的话会先在 ht[0] 中找,如果找不到会到 ht[1] 里面进行查找。

    在渐进式 rehash 的执行期间,所有的添加的新键值对都会保存到 ht[1] 里,ht[0] 不会再执行任何添加操作,保证了 ht[0] 中键值对的数量只减不增。

    字典的主要操作 API

    本文参考《Redis设计与实现》

     

  • 相关阅读:
    POJ 1185 状压DP
    POJ 1321
    hdu 1384 查分约束
    hdu 2196 树形dp
    hdu 4612 双联通缩点+树形dp
    poj 3469 最小割模板sap+gap+弧优化
    hdu 4858 容器的简单模拟
    hdu 4857 逆向拓扑排序+反向输出
    isap算法模板poj 1273gap+弧优化 最大流
    ISAP 算法的学习
  • 原文地址:https://www.cnblogs.com/cuilichao/p/15703926.html
Copyright © 2020-2023  润新知