哈希节点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;
}