zoukankan      html  css  js  c++  java
  • Redis数据结构-字典

    1. 简介

    字典在Redis中应用广泛,Redis数据库的底层就是使用字典来实现的,对数据库的增删查改也都是建立在字典的操作之上的。

    此外,字典还是哈希键的实现之一。

    2. 实现

    2.1 哈希表

    哈希表由dict.h/dictht 结构定义:

    typedef struct dictht {
        // 哈希表数组
        dictEntry **table;
        //哈希表大小
        unsigned long size;
        //哈希表大小掩码,用于计算索引值,总是等于size-1
        unsigned long sizemask;
        //该哈希表已有节点数量
        unsigned long used;
    } dictht;
    

    2.2 哈希表节点

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

    typedef struct dictEntry {
        //键
        void *key;
        //值
        union {
            void *val;
            uint64_t u64;
            int64_t s64;
            double d;
        } v;
        //指向下一个哈希表节点,形成链表
        struct dictEntry *next;
    } dictEntry;
    

    next属性是指向另一个哈希表节点的指针,这个指针可以将多个哈希值相同的键值对连接在一起,以此来解决键值冲突的问题(哈希冲突)。

    3.3 字典

    字典由dict.h/dict结构表示:

    typedef struct dict {
        //类型特定函数
        dictType *type;
        //私有数据
        void *privdata;
        //哈希表
        dictht ht[2];
        //rehash索引
        long rehashidx; /* rehashing not in progress if rehashidx == -1 */
        int iterators; /* number of iterators currently running */
    } dict;
    

    type属性和privdata属性是针对不同类型的键值对,为创建多态字典而设置的:

    • type属性是一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数,Redis会为用途不同的字典设置不同的类型特定函数。
    • privdata属性则保存了需要传给那些类型特定函数的可选参数。
    typedef struct dictType {
        //计算hash值的函数
        unsigned int (*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;
    

    所以一个字典里是包含两个哈希表,ht。 一般情况下,字典只使用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用。

    3. 哈希算法

    当要将一个新的键值对添加到字典里面时,程序需要先根据键值对的键计算出哈希值和索引值,然后再根据索引值,将包含新键值对的哈希表节点先放到哈希表数组的指定索引上面。

    4. 键冲突

    从字典整个hash表设计上看,就知道它使用链地址法解决has冲突。在哈希节点都有一个next指针构成一个单向链表。

    由于没有指向链表尾部的指针,因此为了速度考虑,程序总是将新节点添加到链表的表头位置。

    5. rehash

    为了让hash表的负载因子维持在一个比较合理的范围,当hash表保存的键值对数量太或者太少时,程需要对哈希表的大小进行相应的扩展或者收缩。
    扩展和收hash表的工作通过rehash实现,步骤如下:

    1. 为字典ht[1]哈希表分配空间,这个哈希表的空间大小取决于要执行的操作,以及ht[0]当前包含的键值对数量(也就是ht[0].used属性的值):
    2. 如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used*2的2^n;
    3. 执行的是收缩操作,那么ht[1]的大小为第一个大于等于ht[0].used的2^n。
    4. 将保存在ht[0]中的所有键值对rehash到ht[1]上,rehash指的是重新计算hash值和索引值,然后将键值对放置到ht[1]哈希表的指定位置上。
    5. 当ht[0]包含的所有键值对都迁移到ht[1]之后,释放ht[0],将ht[1]设置为ht[0],并在ht[1]新创建一个空白的哈希表,为下一次rehash做准备。

    6. 渐进式rehash

    为避免大数据量一次性rehash可能对服务器性能造成的影响,服务器不是一次性地将ht[0]里面所有的键值对全部rehash到ht[1],而是分多次、渐进式地将ht[0]里面的键值对慢慢地rehash到ht[1]:

    1. 为ht[1]分配空间;
    2. 在字典中维持一个索引计数器rehashidx,并将它置为0,表示rehash工作正式开始。
    3. 在rehash进行期间,每次对字典执行添加、删除、查找或者更新操作时,程序出了执行执行指定的操作外,还会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1],当rehash工作完成后,程序将rehashidx属性的值增1.
    4. 随着字典操作的不断执行,最终在某个时间点上,ht[0]的所有键值对都会被rehash至ht[1],这是程序将rehashidx属性的值设置为-1,表示rehash操作完成。

    由于在rehash期间,字典同时在使用两个哈希表,所以字典的删除、查找、更新等操作会在两个哈希表上进行;而添加操作则会在ht[1]上进行,不对ht[0]进行操作,保证ht[0]只减不增。

  • 相关阅读:
    Advanced Configuration Tricks
    Reviewing the Blog Module
    Editing and Deleting Data
    Making Use of Forms and Fieldsets
    Understanding the Router
    SQL Abstraction and Object Hydration
    Preparing for Different Databases
    Java学习理解路线图
    Openstack学习历程_1_视频
    CentOS安装Nginx负载
  • 原文地址:https://www.cnblogs.com/xl2432/p/12743874.html
Copyright © 2011-2022 走看看