Redis深度历险-字典迭代器
在Redis中比较重要的一部分就是迭代器,因为Redis是单线程运行的,必须为外部提供
O(1)
级别的接口;包括集合以及哈希表都是以字典来实现的,如hscan
接口;代码在src/dict.c
和hiredis/dict.c
结构体定义
typedef struct dictIterator {
dict *d; //所属字典
long index; //index就是迭代器当前所在的字典数组索引
int table, safe; //当safe为true时会暂停rehash操作,table表示当前在字典中哪个hash表
dictEntry *entry, *nextEntry; //迭代器当前指向的数据及其下一个
long long fingerprint; //一个64bit的数字用来表示字典状态
} dictIterator;
这里的safe
是针对rehash
来说的,会暂停rehash
;字典的渐进式rehash
是通过两个ht
数组实现的,这里的table
字段就是用来表示当前在哪个ht
中的fingerprint
是字典的指纹,在创建迭代器时生成、在销毁迭代器时比较,如果发现两者不一致则表示在迭代过程中进行了异常操作
迭代
创建迭代器
dictIterator *dictGetIterator(dict *d)
{
dictIterator *iter = zmalloc(sizeof(*iter));
iter->d = d;
iter->table = 0;
iter->index = -1;
iter->safe = 0;
iter->entry = NULL;
iter->nextEntry = NULL;
return iter;
}
dictIterator *dictGetSafeIterator(dict *d) {
dictIterator *i = dictGetIterator(d);
i->safe = 1;
return i;
}
开始迭代
dictEntry *dictNext(dictIterator *iter)
{
while (1) {
//因为Redis中以链表法来解决冲突,所以一个索引下可能会有多个值
//entry就是一个链表,不为空则走向entry下一个节点,为空则走向下一个index索引
if (iter->entry == NULL) {
dictht *ht = &iter->d->ht[iter->table];
//在刚开始迭代时进行一些处理
if (iter->index == -1 && iter->table == 0) {
//如果有safe标记则暂停渐进式rehash;否则记录字典指纹
if (iter->safe)
dictPauseRehashing(iter->d);
else
iter->fingerprint = dictFingerprint(iter->d);
}
iter->index++;
//如果走到了当前hash表最后一个数据则尝试切换hash表
if (iter->index >= (long) ht->size) {
//如果正在进行rehash操作,那么可能ht[1]也有部分数据,将table切换到1上
if (dictIsRehashing(iter->d) && iter->table == 0) {
iter->table++;
iter->index = 0;
ht = &iter->d->ht[1];
} else {
break;
}
}
iter->entry = ht->table[iter->index];
} else {
iter->entry = iter->nextEntry;
}
if (iter->entry) {
//这里主要是防止返回的entry被外部销毁掉了
iter->nextEntry = iter->entry->next;
return iter->entry;
}
}
return NULL;
}
很简单的操作就是顺着字典哈希表数组往下走,主要是注意哈希表每个索引上都是一个链表
释放迭代器
void dictReleaseIterator(dictIterator *iter)
{
if (!(iter->index == -1 && iter->table == 0)) {
if (iter->safe)
//重新开始rehash操作
dictResumeRehashing(iter->d);
else
//防止迭代过程中进行了异常操作
assert(iter->fingerprint == dictFingerprint(iter->d));
}
zfree(iter);
}