欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

redis源码之字典dict

程序员文章站 2022-05-20 22:13:39
...

未完待续…

字典dict

1.简介:

它支持插入、删除、替换、查找和获取随机元素等操作。
哈希表会自动在表的大小的二次方之间进行调整。
键的冲突通过链表来解决。
rehash

2.定义

/*
 * 1.哈希表节点
 */
typedef struct dictEntry {

    // 键
    void *key;

    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;

    // 指向下个哈希表节点,形成链表
    struct dictEntry *next;

} dictEntry;

/*
 * 2.哈希表
 * 每个字典都使用两个哈希表,从而实现渐进式 rehash 。
 */
typedef struct dictht {

    // 哈希表数组
    dictEntry **table;

    // 哈希表大小
    unsigned long size;

    // 哈希表大小掩码,用于计算索引值
    // 总是等于 size - 1
    unsigned long sizemask;

    // 该哈希表已有节点的数量
    unsigned long used;

} dictht;

/*
 * 3.字典
 */
typedef struct dict {

    // 类型特定函数
    dictType *type;

    // 私有数据,保存了需要传给那些类型特定画数的可选参数。
    void *privdata;

    // 哈希表,一般情况下,字典只使用ht[OJ 晴希表, ht[1]晗希表只会在对ht[0]哈希表进行rehash 时使用。
    dictht ht[2];

    // rehash 索引
    // 当 rehash 不在进行时,值为 -1
    int rehashidx; /* rehashing not in progress if rehashidx == -1 */

    // 目前正在运行的安全迭代器的数量
    int iterators; /* number of iterators currently running */

} dict;

/*
 * 4.字典类型特定函数
 */
typedef struct dictType {

    // 计算哈希值的函数
    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;

redis源码之字典dict
redis源码之字典dict

3.hash算法,得到索引值

Murmurhash2和DJBhash
Redis 计算晗希值和索引值的方法如下:
#使用字典设置的哈希函数,计算键key 的哈希值
hash= dict->type->hashFunction(key);
#使用哈希衰的sizemask 属性和哈希值,计算出索引值
#根据情况不同, ht[x]可以是ht[0l 或者ht[1]
index= hash 品dict->ht[x].sizemask;

解决键冲突
链地址法(因为 dictEntry 节点组成的链表没有指向链表表尾的指针, 所以为了速度考虑,程序总是将新节点添加到链表的表头位置(复杂度为O(1)), 排在其他已有节点的前面。 )

4.rehash

为了让晗希表的负载因子( load factor )维持在一个合理的范围之内,当哈希表保存的键值对数量太多或者太少时,程序需要对晗希表的大小进行相应的扩展或者收缩。
(1)为字典的ht[1]哈希表分配空间:
(1)如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht[0] .used*2
的2”( 2 的n 次方幕);
(2)如果执行的是收缩操作,那么ht[1]的大小为第一个大于等于ht[0] .used的2”;、
(2)将保存在ht[0]中的所有键值对rehash 到ht[1]上面: rehash 指的是重新计算键的晗希值和索引值,然后将键值对放置到ht[1]哈希表的指定位置上。
(3)当ht[0]包含的所有键值对都迁移到了ht[1]之后( ht [0]变为空表),释放
ht[0],将ht [1]设置为ht[0] ,并在ht[1]新创建一个空白哈希表,为下一次rehash
做准备。
哈希表的扩展与收缩条件
负载因子 = 哈希表已保存节点数量 / 哈希表大小,load factor= ht[O].used I ht[OJ.size
负载因子大于1是因为,size是table的数量,而每个table还有链表呢 !
当以下条件中的任意一个被满足时,程序会自动开始对晗希表执行扩展操作:
(1)服务器目前没有在执行 BGSAVE 命令或者 BGREWRITEAOF 命令, 并且哈希表的负载因子大于于 1 ;
(2)服务器目前正在执行 BGSAVE 命令或者 BGREWRITEAOF 命令, 并且哈希表的负载因子大于等于 5 ;
当晗希表的负载因子小于0.1 时,程序自动开始对晗希表执行收缩操作。
在执行BGSA阳命令或BGREWRITEAOF 命令的过程中, Redis 需要创建当前服务器进程的子进程,而大多数操作系统都采用写时复制( copy-onwrite)技术来优化子进程的使用效率,所以在子进程存在期间,服务器会提高执行扩展操作所需的负载因子,从而尽可能地避免在子进程存在期间进行晗希表扩展操作,这可以避免不必要的内存写入操作,最大限度地节约内存。

5.渐进式 rehash:索引0->sizemask

rehash 动作并不是一次性、集中式地完成的,而是分多次、渐进式地完成的。原因在于,一次性将这些键值对全部rehash到ht[1]的话,庞大的计算量可能会导致服务器在一段时间内停止服务。
以下是哈希表渐进式 rehash 的详细步骤:
(1)为 ht[1] 分配空间, 让字典同时持有 ht[0] 和 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 操作已完成。

1.因为在进行渐进式 rehash 的过程中, 字典会同时使用 ht[0] 和 ht[1] 两个哈希表, 所以在渐进式 rehash 进行期间, 字典的删除(delete)、查找(find)、更新(update)等操作会在两个哈希表上进行: 比如说, 要在字典里面查找一个键的话, 程序会先在ht[0] 里面进行查找, 如果没找到的话, 就会继续到 ht[1] 里面进行查找, 诸如此类。
2.另外, 在渐进式 rehash 执行期间, 新添加到字典的键值对一律会被保存到 ht[1] 里面, 而 ht[0] 则不再进行任何添加操作: 这一措施保证了 ht[0] 包含的键值对数量会只减不增, 并随着 rehash 操作的执行而最终变成空表。

typedef struct dictIterator {
    // 被迭代的字典
    dict *d;
    // table :正在被迭代的哈希表号码,值可以是 0 或 1 。
    // index :迭代器当前所指向的哈希表索引位置。
    // safe :标识这个迭代器是否安全
    int table, index, safe;
    // entry :当前迭代到的节点的指针
    // nextEntry :当前迭代节点的下一个节点
    // 因为在安全迭代器运作时, entry 所指向的节点可能会被修改,
    // 所以需要一个额外的指针来保存下一节点的位置,
    // 从而防止指针丢失
    dictEntry *entry, *nextEntry;
    long long fingerprint; // unsafe iterator fingerprint for misuse detection
} dictIterator;