Redis源码阅读(四)dict.h和dict.c

哈希节点dictEntry

key*用来保存键,union用来保存值,next指向下一个哈希节点,metedata是由dictType的dictEntryMetadataBytes()返回的任意字节数(从指针对齐的地址开始)。

typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;     /* Next entry in the same hash bucket. */
    void *metadata[];           /* An arbitrary number of bytes (starting at a
                                 * pointer-aligned address) of size as returned
                                 * by dictType's dictEntryMetadataBytes(). */
} dictEntry;

字典

type表示类型特定的函数(见下一标题详细)
ht_table是哈希表 [2]是因为需要hash扩容
ht_used是表示有多少个 键值对实体(dictEntry),used越多,哈希冲突的情况越多
pauserehash >0表示暂停 小于0表示出错
ht_size_exp表示哈希桶大小
rehashidx是rehash索引 rehash不在进行时 值为-1

typedef struct dict dict;
struct dict {
    dictType *type;

    dictEntry **ht_table[2];
    unsigned long ht_used[2];

    long rehashidx; /* rehashing not in progress if rehashidx == -1 */

    /* Keep small vars at end for optimal (minimal) struct padding */
    int16_t pauserehash; /* If >0 rehashing is paused (<0 indicates coding error) */
    signed char ht_size_exp[2]; /* exponent of size. (size = 1<<exp) */
};

dictType

hashFunction 计算哈希值函数
keyDup 复制键的函数
valDup复制值的函数
keyCompare对比键的函数
keyDestructor销毁键
valDestructor销毁值
dictEntryMetadataBytes:允许dictEntry携带额外的调用者定义的元数据。在分配字典try时,额外的内存被初始化为0。(redis7.0后才有的)

typedef struct dictType {
    uint64_t (*hashFunction)(const void *key);
    void *(*keyDup)(dict *d, const void *key);
    void *(*valDup)(dict *d, const void *obj);
    int (*keyCompare)(dict *d, const void *key1, const void *key2);
    void (*keyDestructor)(dict *d, void *key);
    void (*valDestructor)(dict *d, void *obj);
    int (*expandAllowed)(size_t moreMem, double usedRatio);
    /* Allow a dictEntry to carry extra caller-defined metadata.  The
     * extra memory is initialized to 0 when a dictEntry is allocated. */
    size_t (*dictEntryMetadataBytes)(dict *d);
} dictType;

redis哈希桶,哈希节点,key,value的关系如下图
在这里插入图片描述

宏函数

DICTHT_SIZE(exp)计算位移后的大小
DICTHT_SIZE_MASK(exp) 计算位移exp位-1的大小

#define DICTHT_SIZE(exp) ((exp) == -1 ? 0 : (unsigned long)1<<(exp))
#define DICTHT_SIZE_MASK(exp) ((exp) == -1 ? 0 : (DICTHT_SIZE(exp))-1)

字典迭代器

dict *d字典的首节点
long index 索引,某条哈希链的索引值
table 哈希桶的数字的索引,table=0表示第一条哈希链
safe安全模式可以调用别的函数,不安全的之恶能依次遍历
entry当前哈希节点,next下一个哈希节点
fingerprint 用于不安全迭代器的错误检测

/* 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. */
typedef struct dictIterator {
    dict *d;
    long index;
    int table, safe;
    dictEntry *entry, *nextEntry;
    /* unsafe iterator fingerprint for misuse detection. */
    unsigned long long fingerprint;
} dictIterator;

回调函数

用时决定,估计会等待进一步的封装

typedef void (dictScanFunction)(void *privdata, const dictEntry *de);
typedef void (dictScanBucketFunction)(dict *d, dictEntry **bucketref);

宏定义之初始大小

1<<2,初始大小为4

/* This is the initial size of every hash table */
#define DICT_HT_INITIAL_EXP      2
#define DICT_HT_INITIAL_SIZE     (1<<(DICT_HT_INITIAL_EXP))

宏函数

大部分看驼峰命名就能知道意思,就不赘述。

/* ------------------------------- Macros ------------------------------------*/
#define dictFreeVal(d, entry) do {                     \
    if ((d)->type->valDestructor)                      \
        (d)->type->valDestructor((d), (entry)->v.val); \
   } while(0)

#define dictSetVal(d, entry, _val_) do { \
    if ((d)->type->valDup) \
        (entry)->v.val = (d)->type->valDup((d), _val_); \
    else \
        (entry)->v.val = (_val_); \
} while(0)

#define dictSetSignedIntegerVal(entry, _val_) \
    do { (entry)->v.s64 = _val_; } while(0)

#define dictSetUnsignedIntegerVal(entry, _val_) \
    do { (entry)->v.u64 = _val_; } while(0)

#define dictSetDoubleVal(entry, _val_) \
    do { (entry)->v.d = _val_; } while(0)

#define dictFreeKey(d, entry) \
    if ((d)->type->keyDestructor) \
        (d)->type->keyDestructor((d), (entry)->key)

#define dictSetKey(d, entry, _key_) do { \
    if ((d)->type->keyDup) \
        (entry)->key = (d)->type->keyDup((d), _key_); \
    else \
        (entry)->key = (_key_); \
} while(0)

#define dictCompareKeys(d, key1, key2) \
    (((d)->type->keyCompare) ? \
        (d)->type->keyCompare((d), key1, key2) : \
        (key1) == (key2))
## 获取元数据
#define dictMetadata(entry) (&(entry)->metadata)
#define dictMetadataSize(d) ((d)->type->dictEntryMetadataBytes \
                             ? (d)->type->dictEntryMetadataBytes(d) : 0)

#define dictHashKey(d, key) ((d)->type->hashFunction(key))
#define dictGetKey(he) ((he)->key)
#define dictGetVal(he) ((he)->v.val)
#define dictGetSignedIntegerVal(he) ((he)->v.s64)
#define dictGetUnsignedIntegerVal(he) ((he)->v.u64)
#define dictGetDoubleVal(he) ((he)->v.d)
#获取hash和rehash的需要的空间大小
#define dictSlots(d) (DICTHT_SIZE((d)->ht_size_exp[0])+DICTHT_SIZE((d)->ht_size_exp[1]))
#获取hash和rehash的需要的空间大小
#define dictSize(d) ((d)->ht_used[0]+(d)->ht_used[1])
#获取rehashidx是不是为-1-1表示正在进行)
#define dictIsRehashing(d) ((d)->rehashidx != -1)
#停止rehash时,pauserehash加一
#define dictPauseRehashing(d) ((d)->pauserehash++)
#继续rehash时,pauserehash减一
#define dictResumeRehashing(d) ((d)->pauserehash--)

dict的api函数

函数名备注
dict *dictCreate(dictType *type);
int dictExpand(dict *d, unsigned long size);
int dictTryExpand(dict *d, unsigned long size);
int dictAdd(dict *d, void *key, void *val);
dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing);
dictEntry *dictAddOrFind(dict *d, void *key);
int dictReplace(dict *d, void *key, void *val);
int dictDelete(dict *d, const void *key);
dictEntry *dictUnlink(dict *d, const void *key);
void dictFreeUnlinkedEntry(dict *d, dictEntry *he);
void dictRelease(dict *d);
dictEntry * dictFind(dict *d, const void *key);
void *dictFetchValue(dict *d, const void *key);
int dictResize(dict *d);
dictIterator *dictGetIterator(dict *d);
dictIterator *dictGetSafeIterator(dict *d);
void dictInitIterator(dictIterator *iter, dict *d);
void dictInitSafeIterator(dictIterator *iter, dict *d);
void dictResetIterator(dictIterator *iter);
dictEntry *dictNext(dictIterator *iter);
void dictReleaseIterator(dictIterator *iter);
dictEntry *dictGetRandomKey(dict *d);
dictEntry *dictGetFairRandomKey(dict *d);
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count);
void dictGetStats(char *buf, size_t bufsize, dict *d);
uint64_t dictGenHashFunction(const void *key, size_t len);
uint64_t dictGenCaseHashFunction(const unsigned char *buf, size_t len);
void dictEmpty(dict d, void(callback)(dict));
void dictEnableResize(void);
void dictDisableResize(void);
int dictRehash(dict *d, int n);
int dictRehashMilliseconds(dict *d, int ms);
void dictSetHashFunctionSeed(uint8_t *seed);
uint8_t *dictGetHashFunctionSeed(void);
unsigned long dictScan(dict *d, unsigned long v, dictScanFunction *fn, dictScanBucketFunction *bucketfn, void *privdata);
uint64_t dictGetHash(dict *d, const void *key);
dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, uint64_t hash);

创建新hash

/* Create a new hash table */
dict *dictCreate(dictType *type)
{
    dict *d = zmalloc(sizeof(*d));
    _dictInit(d,type);
    return d;
}

/* Initialize the hash table */
int _dictInit(dict *d, dictType *type)//初始时,rehashidx为-1,pauserehash为0
{`在这里插入代码片`
    _dictReset(d, 0);
    _dictReset(d, 1);
    d->type = type;
    d->rehashidx = -1;
    d->pauserehash = 0;
    return DICT_OK;
}
/* Reset hash table parameters already initialized with _dictInit()*/
static void _dictReset(dict *d, int htidx) //ht_table[htidx]置空 size_exp为初始值 使用空间为0
{
    d->ht_table[htidx] = NULL;
    d->ht_size_exp[htidx] = -1;
    d->ht_used[htidx] = 0;
}

重新分配空间

static int dict_can_resize = 1;//可以resize为非0

/* Resize the table to the minimal size that contains all the elements,
 * but with the invariant of a USED/BUCKETS ratio near to <= 1 */
#重新分配空间时,分配可以用的最小空间和初始空间的
int dictResize(dict *d)
{
    unsigned long minimal;
    if (!dict_can_resize || dictIsRehashing(d)) return DICT_ERR;
    minimal = d->ht_used[0];
    if (minimal < DICT_HT_INITIAL_SIZE)
        minimal = DICT_HT_INITIAL_SIZE;
    return dictExpand(d, minimal);
}

/* return DICT_ERR if expand was not performed */
int dictExpand(dict *d, unsigned long size) {
    return _dictExpand(d, size, NULL);

/* Expand or create the hash table,
 * when malloc_failed is non-NULL, it'll avoid panic if malloc fails (in which case it'll be set to 1).
 * Returns DICT_OK if expand was performed, and DICT_ERR if skipped. */
int _dictExpand(dict *d, unsigned long size, int* malloc_failed)
{
    if (malloc_failed) *malloc_failed = 0;
    /* the size is invalid if it is smaller than the number of
     * elements already inside the hash table */
    ##正在rehash和使用空间不足的不可扩容
    if (dictIsRehashing(d) || d->ht_used[0] > size)
        return DICT_ERR;

    /* the new hash table */
    dictEntry **new_ht_table;
    unsigned long new_ht_used;
    signed char new_ht_size_exp = _dictNextExp(size);

    /* Detect overflows */
    size_t newsize = 1ul<<new_ht_size_exp;
    if (newsize < size || newsize * sizeof(dictEntry*) < newsize)
        return DICT_ERR;
	#新的分配空间跟原本的一样
    /* Rehashing to the same table size is not useful. */
    if (new_ht_size_exp == d->ht_size_exp[0]) return DICT_ERR;
   	##分配失败
    /* Allocate the new hash table and initialize all pointers to NULL */
    if (malloc_failed) {
        new_ht_table = ztrycalloc(newsize*sizeof(dictEntry*));
        *malloc_failed = new_ht_table == NULL;
        if (*malloc_failed)
            return DICT_ERR;
    } else
        new_ht_table = zcalloc(newsize*sizeof(dictEntry*));

    new_ht_used = 0;

    /* Is this the first initialization? If so it's not really a rehashing
     * we just set the first hash table so that it can accept keys. */
    if (d->ht_table[0] == NULL) {
        d->ht_size_exp[0] = new_ht_size_exp;
        d->ht_used[0] = new_ht_used;
        d->ht_table[0] = new_ht_table;
        return DICT_OK;
    }

    /* Prepare a second hash table for incremental rehashing */
    d->ht_size_exp[1] = new_ht_size_exp;
    d->ht_used[1] = new_ht_used;
    d->ht_table[1] = new_ht_table;
    d->rehashidx = 0;
    return DICT_OK;
}


}


向目标哈希表添加元素

int dictAdd(dict *d, void *key, void *val)
{
    dictEntry *entry = dictAddRaw(d,key,NULL);
    if (!entry) return DICT_ERR;
    dictSetVal(d, entry, val);
    return DICT_OK;
}

dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
{
    long index;
    dictEntry *entry;
    int htidx;
	#正在rehash的调用其他
    if (dictIsRehashing(d)) _dictRehashStep(d);
	#获取key的index,为-1表示已经存在就没有添加
    /* Get the index of the new element, or -1 if
     * the element already exists. */
    if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1)
        return NULL;

    /* Allocate the memory and store the new entry.
     * Insert the element in top, with the assumption that in a database
     * system it is more likely that recently added entries are accessed
     * more frequently. */
    #分配空间后让哈希桶的首元素指向最新添加的哈希节点,哈希节点的下一个节点为操作之前的首节点
    #保证最新添加的元素在头部 因为更容易命中
    htidx = dictIsRehashing(d) ? 1 : 0;
    size_t metasize = dictMetadataSize(d);
    entry = zmalloc(sizeof(*entry) + metasize);
    if (metasize > 0) {
        memset(dictMetadata(entry), 0, metasize);
    }
    entry->next = d->ht_table[htidx][index];
    d->ht_table[htidx][index] = entry;
    d->ht_used[htidx]++;

    /* Set the hash entry fields. */
    #添加空间后再给哈希节点的key赋值(如果插入前进行赋值操作性能没有插入后要好)
    dictSetKey(d, entry, key);
    return entry;
}

#不存在就添加 存在key就覆盖
int dictReplace(dict *d, void *key, void *val)
{
    dictEntry *entry, *existing, auxentry;
    /* Try to add the element. If the key
     * does not exists dictAdd will succeed. */
    entry = dictAddRaw(d,key,&existing);
    if (entry) {
        dictSetVal(d, entry, val);
        return 1;
    }
    /* Set the new value and free the old one. Note that it is important
     * to do that in this order, as the value may just be exactly the same
     * as the previous one. In this context, think to reference counting,
     * you want to increment (set), and then decrement (free), and not the
     * reverse. */
    auxentry = *existing;
    dictSetVal(d, existing, val);
    dictFreeVal(d, &auxentry);
    return 0;
}
#不存在就添加 存在就返回这个节点
dictEntry *dictAddOrFind(dict *d, void *key) {
    dictEntry *entry, *existing;
    entry = dictAddRaw(d,key,&existing);
    return entry ? entry : existing;
}

#查找并返回元素
static dictEntry *dictGenericDelete(dict *d, const void *key, int nofree) {
    uint64_t h, idx;
    dictEntry *he, *prevHe;
    int table;
    /* dict is empty */
    if (dictSize(d) == 0) return NULL;
    if (dictIsRehashing(d)) _dictRehashStep(d);
    #h是在哈希链的位置索引值
    h = dictHashKey(d, key);

    for (table = 0; table <= 1; table++) {
        idx = h & DICTHT_SIZE_MASK(d->ht_size_exp[table]);
        he = d->ht_table[table][idx];
        prevHe = NULL;
        while(he) {
        //he不为null时 这个节点在这条链上,就开始逐个对比
            if (key==he->key || dictCompareKeys(d, key, he->key)) {
                /* Unlink the element from the list */
                if (prevHe)//不为空 不是第一个节点 前一个节点的下一个指向he的下一个
                    prevHe->next = he->next;
                else//第一个节点就是我们要找的节点
                    d->ht_table[table][idx] = he->next;
                if (!nofree) {
                    dictFreeUnlinkedEntry(d, he);
                }
                d->ht_used[table]--;
                return he;
            }
            prevHe = he;
            he = he->next;
        }
        if (!dictIsRehashing(d)) break;
    }
    return NULL; /* not found */
}

/* Remove an element, returning DICT_OK on success or DICT_ERR if the
 * element was not found. */
int dictDelete(dict *ht, const void *key) {
    return dictGenericDelete(ht,key,0) ? DICT_OK : DICT_ERR;
}

清除整个表

/* Destroy an entire dictionary */
int _dictClear(dict *d, int htidx, void(callback)(dict*)) {
    unsigned long i;
    /* Free all the elements */
    for (i = 0; i < DICTHT_SIZE(d->ht_size_exp[htidx]) && d->ht_used[htidx] > 0; i++) {
        dictEntry *he, *nextHe;
        if (callback && (i & 65535) == 0) callback(d);
        if ((he = d->ht_table[htidx][i]) == NULL) continue;
        while(he) {
            nextHe = he->next;
            dictFreeKey(d, he);
            dictFreeVal(d, he);
            zfree(he);
            d->ht_used[htidx]--;
            he = nextHe;
        }
    }
    /* Free the table and the allocated cache structure */
    zfree(d->ht_table[htidx]);
    /* Re-initialize the table */
    _dictReset(d, htidx);
    return DICT_OK; /* never fails */
}
/* Clear & Release the hash table */
void dictRelease(dict *d)
{
    _dictClear(d,0,NULL);
    _dictClear(d,1,NULL);
    zfree(d);
}

渐进式rehash

dictAdd、dictDelete、dictFind中都有这两个函数,_dictRehashStep和_dictExpandIfNeeded,这两个步骤都是为dict扩容服务。当dict中元素达到一定阈值时,Redis中规定的阈值是数组大小,为了避免dict的查询效率下降,就需要扩容。对于Redis来说,由于其单线程的模型,使得任何操作都不能占用大多时间,从而造成服务抖动,同时如果dict过大时,一次扩容有可能消耗较长时间。所以Redis中采用了渐进式rehash进行扩容的做法。
首先来看一下扩容触发条件,主要有两个参数控制:

dict_can_resize,控制是否可以进行扩容,Redis在做持久化RDB/AOF时,会设置dict_can_resize为0,禁止内部的dict结构扩容。为什么呢?在进行RDB/AOF时,Redis通过fork()系统调用创建子进程来复制一份内存做快照,而fork()的原理是Copy On Write,如果系统内存在子进程存在这段时间持续变化的话,会严重放大COW。
dict_force_resize_ratio,dict的使用率,默认为1,避免内存元素过多造成的查询性能下降

执行N步增量哈希。如果仍然有键要从旧哈希表移动到新的哈希表,则返回1,否则返回0。
注意,重哈希步骤包括将一个桶(在使用链接时可能有多个键)从旧哈希表移动到新的哈希表,然而,由于哈希表的一部分可能由空格组成,不能保证这个函数会重哈希哪怕是单个桶,因为它将访问最多N*10个空桶,否则的数量它所做的工作将被解除绑定,函数可能会阻塞很长时间

int dictRehash(dict *d, int n) {
	##最多访问n*10个bucket,如果连着n*10个bucket都是空的话,直接返回成功
    int empty_visits = n*10; /* Max number of empty buckets to visit. */
    if (!dictIsRehashing(d)) return 0;
	//防止rehashidx溢出
    while(n-- && d->ht_used[0] != 0) {
        dictEntry *de, *nextde;
        //连着n*10个bucket都是空的话,直接返回成功
        assert(DICTHT_SIZE(d->ht_size_exp[0]) > (unsigned long)d->rehashidx);
        while(d->ht_table[0][d->rehashidx] == NULL) {
            d->rehashidx++;
            if (--empty_visits == 0) return 1;
        }
         //这个rehashidx是bucket的索引,有效的话把该bucket的dictEntry链全部移动
        de = d->ht_table[0][d->rehashidx];
        /* Move all the keys in this bucket from the old to the new hash HT */
        while(de) {
            uint64_t h;
            nextde = de->next;
            /* Get the index in the new hash table */
            h = dictHashKey(d, de->key) & DICTHT_SIZE_MASK(d->ht_size_exp[1]);
            #de就是哈希桶的当前需要转移的元素 h是de在原哈希链的位置索引
            #让de的next指向新哈希链的下一个位置
            de->next = d->ht_table[1][h];
             #让d->ht_table[1][h]=de 修改新哈希链结点值
            d->ht_table[1][h] = de;
            #修改使用元素的大小,然后再让de指向下一个原哈希链的位置
            d->ht_used[0]--;
            d->ht_used[1]++;
            de = nextde;
        }
        #原哈希链滞空
        d->ht_table[0][d->rehashidx] = NULL;
        d->rehashidx++;
    }
    /* Check if we already rehashed the whole table... */
    if (d->ht_used[0] == 0) {
        zfree(d->ht_table[0]);
        /* Copy the new ht onto the old one */
        d->ht_table[0] = d->ht_table[1];
        d->ht_used[0] = d->ht_used[1];
        d->ht_size_exp[0] = d->ht_size_exp[1];
        _dictReset(d, 1);
        d->rehashidx = -1;
        return 0;
    }
    /* More to rehash... */
    return 1;
}

dictScan() 反向二进制迭代法

dictScan()用于遍历字典中的元素。
迭代的工作方式如下:
1)最初调用函数时使用游标(v)值为0。
2)函数执行迭代的一个步骤,并返回必须在下次调用中使用的新游标值。
3)当返回的游标为0时,迭代完成。
该函数保证在迭代开始和结束之间返回字典中存在的所有元素。
然而,有些元素可能会被多次返回。
对于每个返回的元素,回调参数’fn’被调用,'privdata’作为第一个参数,哈希节点’de’作为第二个参数。

其主要思想是从高阶位开始递增游标。也就是说,不是将游标正常递增,而是将游标的位反转,然后将游标递增,最后再将位反转。
这种策略是必需的,因为哈希表可能在迭代调用之间调整大小。
哈希表的大小总是2的幂,而且它们使用链表,所以给定表中元素的位置是通过计算hash (key)和size -1之间的位与来给出的(其中SIZE-1始终是掩码,相当于在键的散列和SIZE之间取除法的其余部分)
例如,如果当前哈希表大小为16,则掩码为(二进制)1111。键在哈希表中的位置将始终是哈希输出的最后四位,以此类推。

如果哈希表增长,元素可以在旧桶的一个倍数内的任何位置:例如,假设我们已经迭代了一个4位游标1100(掩码为1111,因为哈希表的大小为16)。如果哈希表将被调整为64个元素,那么新的掩码将是111111。通过用0或1替换xx1100获得的新桶,只能通过我们在小哈希表中扫描桶1100时已经访问过的键来定位。由于计数器是反向的,所以先迭代较高的位,如果表的大小变大,则不需要重新启动游标。它将使用游标继续迭代,游标的末尾没有’1100’,也没有已经探索过的最后4位的任何其他组合。类似地,当表的大小随着时间的推移而缩小时,例如从16缩小到8,如果低三位的组合(8号的掩码是111)已经被完全探索过,它不会再次被访问,因为我们确定我们已经尝试过了,例如,0111和1111(高位的所有变体),所以我们不需要再次测试它。

YOU HAVE TWO TABLES DURING REHASHING
但是我们总是先迭代较小的表,然后测试当前游标到较大表的所有扩展。例如,如果当前游标是101,并且我们也有一个大小为16的较大表,那么我们也在较大表中测试(0)101和(1)101。这将问题减少到只有一个表,其中较大的表,如果它存在,只是小表的扩展。

限制
这个迭代器是完全无状态的,这是一个巨大的优势,包括不使用额外的内存。
这种设计带来的缺点是:
1)有可能返回元素不止一次。然而,这在应用程序级别通常很容易处理。
2)迭代器每次调用必须返回多个元素,因为它需要总是返回在给定bucket中链接的所有键和所有展开,所以我们可以确保在重哈希过程中不会错过键的移动。
3)反向光标一开始有点难以理解,但这个注释应该是有帮助的。

//v是游标 fn是回调函数
unsigned long dictScan(dict *d,
                       unsigned long v,
                       dictScanFunction *fn,
                       dictScanBucketFunction* bucketfn,
                       void *privdata)
{
    int htidx0, htidx1;
    const dictEntry *de, *next;
    unsigned long m0, m1;
// 跳过空字典
    if (dictSize(d) == 0) return 0;

    /* This is needed in case the scan callback tries to do dictFind or alike. */
    dictPauseRehashing(d);
	//迭代只有一个哈希表的字典
    //没有在做rehash,所以只有第一个表有数据的
    if (!dictIsRehashing(d)) {
    	// 指向哈希表
        htidx0 = 0;
        m0 = DICTHT_SIZE_MASK(d->ht_size_exp[htidx0]);

        /* Emit entries at cursor */
        if (bucketfn) bucketfn(d, &d->ht_table[htidx0][v & m0]);
        // 指向哈希桶
        de = d->ht_table[htidx0][v & m0];
        //遍历桶中的所有节点
        while (de) {
            next = de->next;
            fn(privdata, de);
            de = next;
        }

        /* Set unmasked bits so incrementing the reversed cursor
         * operates on the masked bits */
        v |= ~m0;
		
		//000->100->010->110->001->101->011->111
        /* Increment the reverse cursor */
        v = rev(v);
        v++;
        v = rev(v);
 // 迭代有两个哈希表的字典
    } else {
    // 指向两个哈希表
        htidx0 = 0;
        htidx1 = 1;

        /* Make sure t0 is the smaller and t1 is the bigger table */
        // 确保 htidx0 比 htidx1 要小
        if (DICTHT_SIZE(d->ht_size_exp[htidx0]) > DICTHT_SIZE(d->ht_size_exp[htidx1])) {
            htidx0 = 1;
            htidx1 = 0;
        }

        m0 = DICTHT_SIZE_MASK(d->ht_size_exp[htidx0]);
        m1 = DICTHT_SIZE_MASK(d->ht_size_exp[htidx1]);

        /* Emit entries at cursor */
        if (bucketfn) bucketfn(d, &d->ht_table[htidx0][v & m0]);
        de = d->ht_table[htidx0][v & m0];
        // 指向桶,并迭代桶中的所有节点 ,处理小一点的表。
        while (de) {
            next = de->next;
            fn(privdata, de);
            de = next;
        }

        /* Iterate over indices in larger table that are the expansion
         * of the index pointed to by the cursor in the smaller table */
          //扫描大点的表里面的槽位,注意这里是个循环,会将小表没有覆盖的slot全部扫描一次的
        do {
            /* Emit entries at cursor */
            if (bucketfn) bucketfn(d, &d->ht_table[htidx1][v & m1]);
            de = d->ht_table[htidx1][v & m1];
            while (de) {
                next = de->next;
                fn(privdata, de);
                de = next;
            }

            /* Increment the reverse cursor not covered by the smaller mask.*/
            v |= ~m1;
            v = rev(v);
            v++;
            v = rev(v);

            /* Continue while bits covered by mask difference is non-zero */
        } while (v & (m0 ^ m1));
    }

    dictResumeRehashing(d);

    return v;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值