美文网首页我爱编程
Redis 源码研究之dict

Redis 源码研究之dict

作者: wenmingxing | 来源:发表于2018-04-13 15:04 被阅读53次

    本文主要记录在阅读Redis源码中dict部分的一些函数和实现的巧妙之处。

    建议阅读:

    1、Redis字典实现的理论说明见: wenmingxing Redis之字典
    2、完成的代码注释见:Redis3.0_sourcecode_reading

    I、哈希表节点

    /*
     * 哈希表节点
     */
    typedef struct dictEntry {
        
        // 键
        void *key;
    
        // 值,可以是三种形式
        union {
            void *val;
            uint64_t u64;
            int64_t s64;
        } v;
    
        // 指向下个哈希表节点,形成链表
        struct dictEntry *next;
    
    } dictEntry;
    

    II、哈希表结构

    dictEntry结构组成哈希表链表。

    /* This is our hash table structure. Every dictionary has two of this as we
     * implement incremental rehashing, for the old to the new table. */
    /*
     * 哈希表
     *
     * 每个字典都使用两个哈希表,从而实现渐进式 rehash 。
     */
    typedef struct dictht {
        
        // 哈希表数组
        // 里面存储dictEntry
        dictEntry **table;
    
        // 哈希表大小
        unsigned long size;
        
        // 哈希表大小掩码,用于计算索引值
        // 总是等于 size - 1,index = hash & sizemask
        // 这样所有计算得出的hash值都可以对应于table的索引
        unsigned long sizemask;
    
        // 该哈希表已有节点的数量
        unsigned long used;
    
    } dictht;
    

    III、字典结构

    /*
     * 字典
     */
    typedef struct dict {
    
        // 类型特定函数
        dictType *type;
    
        // 私有数据
        void *privdata;
    
        // 哈希表,每个字典包含两个table
        dictht ht[2];
    
        // rehash 索引标识
        // 当 rehash 不在进行时,值为 -1
        int rehashidx; /* rehashing not in progress if rehashidx == -1 */
    
        // 目前正在运行的安全迭代器的数量
        int iterators; /* number of iterators currently running */
    
    } dict;
    

    IV、渐进式rehash函数实现源码

    /* Performs N steps of incremental rehashing. Returns 1 if there are still
     * keys to move from the old to the new hash table, otherwise 0 is returned.
     *
     * 执行 N 步渐进式 rehash 。
     *
     * 返回 1 表示仍有键需要从 0 号哈希表移动到 1 号哈希表,
     * 返回 0 则表示所有键都已经迁移完毕。
     *
     * Note that a rehashing step consists in moving a bucket (that may have more
     * than one key as we use chaining) from the old to the new hash table.
     *
     * 注意,每步 rehash 都是以一个哈希表索引作为单位的,这个哈希表的索引可以看做是桶(联想桶排序)。
     * 一个桶里可能会有多个节点,被 rehash 的桶里的所有节点都会被移动到新哈希表。
     *
     * T = O(N)
     */
    int dictRehash(dict *d, int n) {
    
        // dictIsRehashing标识是否在执行rehash
        // 只可以在 rehash 进行中时执行
        if (!dictIsRehashing(d)) return 0;
    
        // 进行 N 步迁移
        // T = O(N)
        while(n--) {
            dictEntry *de, *nextde;    
    
            /* Check if we already rehashed the whole table... */
            // 如果 0 号哈希表为空,那么表示 rehash 执行完毕
            // T = O(1)
            if (d->ht[0].used == 0) {
                // 释放 0 号哈希表
                zfree(d->ht[0].table);
                // 将原来的 1 号哈希表设置为新的 0 号哈希表
                d->ht[0] = d->ht[1];
                // 重置旧的 1 号哈希表
                _dictReset(&d->ht[1]);
                // 关闭 rehash 标识
                d->rehashidx = -1;
                // 返回 0 ,向调用者表示 rehash 已经完成
                return 0;
            }
    
            /* Note that rehashidx can't overflow as we are sure there are more
             * elements because ht[0].used != 0 */
            // 断言,确保 rehashidx 没有越界
            assert(d->ht[0].size > (unsigned)d->rehashidx);
    
            // 略过数组中为空的索引,找到下一个非空索引
            while(d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++;
    
            // de指向该索引的链表表头节点
            de = d->ht[0].table[d->rehashidx];
            /* Move all the keys in this bucket from the old to the new hash HT */
            // 将链表中的所有节点迁移到新哈希表
            // T = O(1)
            while(de) {     //链表中的每个节点都需要重新计算所在位置,而不是将整个链表直接存放到table一个索引中
                unsigned int h;
    
                // 保存下个节点的指针
                nextde = de->next;
    
                /* Get the index in the new hash table */
                // 计算新哈希表的哈希值,以及节点插入的索引位置
                // 索引位置为哈希值&掩码,比如说一个数和8相与,则得到的结果不可能大于8
                h = dictHashKey(d, de->key) & d->ht[1].sizemask;
    
                // 插入节点到新哈希表,每次插入都插入到链表的头结点,这样的时间复杂度为O(1)
                de->next = d->ht[1].table[h];
                d->ht[1].table[h] = de;
    
                // 更新计数器
                d->ht[0].used--;
                d->ht[1].used++;
    
                // 继续处理下个节点
                de = nextde;
            }
            // 将刚迁移完的哈希表索引的指针设为空
            d->ht[0].table[d->rehashidx] = NULL;
            // 更新 rehash 索引
            d->rehashidx++;
        }
    
        return 1;
    }
    

    V、节点删除函数实现

    /* Search and remove an element */
    /*
     * 查找并删除包含给定键的节点
     *
     * 参数 nofree 决定是否调用键和值的释放函数
     * 0 表示调用,1 表示不调用
     *
     * 找到并成功删除返回 DICT_OK ,没找到则返回 DICT_ERR
     *
     * T = O(1)
     */
    static int dictGenericDelete(dict *d, const void *key, int nofree)
    {
        unsigned int h, idx;
        dictEntry *he, *prevHe;
        int table;
    
        // 字典(的哈希表)为空
        if (d->ht[0].size == 0) return DICT_ERR; /* d->ht[0].table is NULL */
    
        // 进行单步 rehash ,T = O(1)
        // 如果字典正在进行rehash,则由于删除操作,rehash渐进一步
        if (dictIsRehashing(d)) _dictRehashStep(d);
    
        // 计算哈希值
        h = dictHashKey(d, key);
    
        // 遍历哈希表
        // T = O(1)
        for (table = 0; table <= 1; table++) {
    
            // 计算索引值 
            idx = h & d->ht[table].sizemask;
            // 指向该索引上的链表
            he = d->ht[table].table[idx];
            prevHe = NULL;
            // 遍历链表上的所有节点
            // T = O(1)
            while(he) {
            
                if (dictCompareKeys(d, key, he->key)) {
                    // 超找目标节点
    
                    /* Unlink the element from the list */
                    // 从链表中删除
                    if (prevHe)
                        prevHe->next = he->next;
                    else    // 如果为头结点
                        d->ht[table].table[idx] = he->next;
    
                    // 释放调用键和值的释放函数
                    if (!nofree) {
                        dictFreeKey(d, he);
                        dictFreeVal(d, he);
                    }
                    
                    // 释放节点本身
                    zfree(he);
    
                    // 更新已使用节点数量
                    d->ht[table].used--;
    
                    // 返回已找到信号
                    return DICT_OK;
                }
    
                prevHe = he;
                he = he->next;
            }
    
            // 如果执行到这里,说明在 0 号哈希表中找不到给定键
            // 那么根据字典是否正在进行 rehash ,决定要不要查找 1 号哈希表
            // 如果没有在执行rehash,则直接退出,最后返回没找到
            if (!dictIsRehashing(d)) break;
        }
    
        // 没找到
        return DICT_ERR; /* not found */
    }
    

    VI、字典的迭代器结构

    /* If safe is set to 1 this is a safe iterator, that means, you can call
     * dictAdd, dictFind, and other functions against the dictionary even while
     * iterating. Otherwise it is a non safe iterator, and only dictNext()
     * should be called while iterating. */
    /*
     * 字典迭代器
     *
     * 如果 safe 属性的值为 1 ,说明是安全迭代器
     * 那么在迭代进行的过程中程序仍然可以执行 dictAdd 、 dictFind 和其他函数,对字典进行修改。
     *
     * 如果 safe 不为 1 ,那么程序只会调用 dictNext 对字典进行迭代,
     * 而不能对字典进行修改。
     */
    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;
    

    VII、迭代器获取函数实现

    主要需要实现的是找到迭代器的下一节点。

    /*
     * 返回迭代器指向的当前节点
     *
     * 字典迭代完毕时,返回 NULL
     *
     * T = O(1)
     */
    dictEntry *dictNext(dictIterator *iter)
    {
        while (1) {
    
            // 进入这个循环有两种可能:
            // 1) 这是迭代器第一次运行
            // 2) 当前索引链表中的节点已经迭代完(NULL 为链表的表尾)
            if (iter->entry == NULL) {      //迭代器第一次运行
    
                // 指向被迭代的哈希表
                dictht *ht = &iter->d->ht[iter->table];
    
                // 初次迭代时执行
                if (iter->index == -1 && iter->table == 0) {
                    // 如果是安全迭代器,那么更新安全迭代器计数器
                    if (iter->safe)
                        iter->d->iterators++;
                    // 如果是不安全迭代器,那么计算指纹
                    else
                        iter->fingerprint = dictFingerprint(iter->d);
                }
                // 更新索引
                iter->index++;
    
                // 如果迭代器的当前索引大于当前被迭代的哈希表的大小
                // 那么说明这个哈希表已经迭代完毕
                if (iter->index >= (signed) ht->size) {
                    // 如果正在 rehash 的话,那么说明 1 号哈希表也正在使用中
                    // 那么继续对 1 号哈希表进行迭代
                    if (dictIsRehashing(iter->d) && iter->table == 0) {
                        iter->table++;
                        iter->index = 0;
                        ht = &iter->d->ht[1];
                    // 如果没有 rehash ,那么说明迭代已经完成
                    } else {
                        break;
                    }
                }
    
                // 如果进行到这里,说明这个哈希表并未迭代完
                // 更新节点指针,指向下个索引链表的表头节点
                iter->entry = ht->table[iter->index];
            } else {
                // 执行到这里,说明程序正在迭代某个链表
                // 将节点指针指向链表的下个节点
                iter->entry = iter->nextEntry;
            }
    
            // 如果当前节点不为空,那么也记录下该节点的下个节点
            // 因为安全迭代器有可能会将迭代器返回的当前节点删除
            if (iter->entry) {
                /* We need to save the 'next' here, the iterator user
                 * may delete the entry we are returning. */
                iter->nextEntry = iter->entry->next;
                return iter->entry;
            }
        }
    
        // 迭代完毕
        return NULL;
    }
    

    【参考】
    [1] 《Redis设计与实现》

    欢迎转载,转载请注明出处wenmingxing Redis源码研究之dict

    相关文章

      网友评论

        本文标题:Redis 源码研究之dict

        本文链接:https://www.haomeiwen.com/subject/lkickftx.html