// 字典中的键值对
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry;
可以发现, 键值对的值是一个union
类型, 既可以表示一个指针
, unit64_t
, int64_t
或者double
数据, 都是8字节的数据. 同时存在一个指向哈希表节点的next
变量, 以此来解决键的冲突的问题(拉链表解决).
typedef struct dictht {
dictEntry **table; // 哈希表数组
unsigned long size; // 哈希表数组的大小
unsigned long sizemask; // 等于size - 1, 用于计算节点的索引值
unsigned long used; // 哈希表包含的节点个数
} dictht;
typedef struct dict {
dictType *type; // 类型特定函数
void *privdata;
dictht ht[2]; // 保存两个哈希列表, 实现渐进式的重散列, ht[0]表示旧哈希列表, ht[1]表示新扩展或压缩新指定的哈希列表
long rehashidx; // 冲散列索引, 表示当前已经进行到的索引, -1表示没有进行重散列
int iterators; // 正在使用的安全模式下iterator的个数, 只有在iterator=0时, 才会进行rehash
} dict;
dictType: 类型特定函数
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;
由于使用数组存储哈希节点, 所以会存在数组空间不足, 需要动态扩充, 由于扩充时, 需要遍历所有节点重新计算节点的索引, 如果一次性完成, 需要消耗一定的时间, 回导致服务器在一段时间内不可用.为了避免重散列对服务器的性能造成影响, 分多次将ht[0]
里的节点重散列到ht[1]
中. 将重散列所需的工作军均摊到对字典的每个添加, 删除, 查找和更新的操作上, 避免了集中式重散列带来的庞大计算量.但是代码的开发难度会明显增大.
同时为了支持创建多态字典, dict
数据结构中指定了dictType
类型变量*type
, 用于操作不同类型的键值对
typedef struct dictIterator {
dict *d; // 被迭代的字典
long index; // 迭代器当前所指向的哈希表索引位置
int table; // 正在被迭代的哈希表, ht[0]和ht[1]
int safe; // 标识迭代器是否处于安全模式
dictEntry *entry; // 当前迭代的节点指针
dictEntry *nextEntry; // 当前迭代节点的下一个节点指针
long long fingerprint; // 非安全模式下使用
} dictIterator;
可以发现, 迭代器的数据结构中含有一个save
字段, 就是为了解决渐进式重散列导致的不安全问题而引入的.
采用渐进式重散列, 迭代遍历时可能会存在的问题:
entry
的失效问题: 因为在遍历列表时, 可能会因为键值过期,而修改列表, 或者插入新的节点, 所以会存在指针安安全问题.ht[0]
, 最后再遍历新的哈希列表ht[1]
, 如果在遍历ht[1]
时, 发生了节点移动, ht[0]
—> ht[1]
, 此时会存在一个节点遍历多次的问题.dictCreate
static void _dictReset(dictht *ht)
{
ht->table = NULL;
ht->size = 0;
ht->sizemask = 0;
ht->used = 0;
}
/* Create a new hash table */
dict *dictCreate(dictType *type,
void *privDataPtr)
{
dict *d = zmalloc(sizeof(*d));
_dictInit(d,type,privDataPtr);
return d;
}
/* Initialize the hash table */
int _dictInit(dict *d, dictType *type,
void *privDataPtr)
{
_dictReset(&d->ht[0]);
_dictReset(&d->ht[1]);
d->type = type;
d->privdata = privDataPtr;
d->rehashidx = -1;
d->iterators = 0;
return DICT_OK;
}
很好理解, 指定类型处理参数, 然后申请dict
内存空间, 最后设置字典的初始值. 可以发现,首次创建的字典中哈希列表的节点数为0.
dictAdd
int dictAdd(dict *d, void *key, void *val)
{
dictEntry *entry = dictAddRaw(d,key);
if (!entry) return DICT_ERR;
dictSetVal(d, entry, val);
return DICT_OK;
}
dictEntry *dictAddRaw(dict *d, void *key)
{
int index;
dictEntry *entry;
dictht *ht;
if (dictIsRehashing(d)) _dictRehashStep(d);
/* Get the index of the new element, or -1 if
* the element already exists. */
if ((index = _dictKeyIndex(d, key)) == -1)
return NULL;
/* Allocate the memory and store the new entry */
ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
entry = zmalloc(sizeof(*entry));
entry->next = ht->table[index];
ht->table[index] = entry;
ht->used++;
/* Set the hash entry fields. */
dictSetKey(d, entry, key);
return entry;
}
ht[1]
大小扩容为4ht[1]
中dictReplace
int dictReplace(dict *d, void *key, void *val)
{
dictEntry *entry, auxentry;
/* Try to add the element. If the key
* does not exists dictAdd will suceed. */
if (dictAdd(d, key, val) == DICT_OK)
return 1;
/* It already exists, get the entry */
entry = dictFind(d, key);
/* 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 = *entry;
dictSetVal(d, entry, val);
dictFreeVal(d, &auxentry);
return 0;
}
dictAdd
添加, 如果添加成功直接返回key
的节点, 使用新值覆盖旧值dictDelete
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 */
if (dictIsRehashing(d)) _dictRehashStep(d);
h = dictHashKey(d, key);
for (table = 0; table <= 1; table++) {
idx = h & d->ht[table].sizemask;
he = d->ht[table].table[idx];
prevHe = NULL;
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;
}
if (!dictIsRehashing(d)) break;
}
return DICT_ERR; /* not found */
}
int dictDelete(dict *ht, const void *key) {
return dictGenericDelete(ht,key,0);
}
ht[0]
中的部分节点移动到ht[1]
DICT_OK
DICT_ERR
// 默认使用非安全模式
dictIterator *dictGetIterator(dict *d)
{
dictIterator *iter = zmalloc(sizeof(*iter));
iter->d = d; // 指定字典
iter->table = 0; // 默认从ht[0]开始遍历
iter->index = -1; // 当前遍历的槽位置,初始化为-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) {
// 遍历新槽位下的链表
if (iter->entry == NULL) {
dictht *ht = &iter->d->ht[iter->table];
// 开始遍历ht[0]中的数据
if (iter->index == -1 && iter->table == 0) {
if (iter->safe)
// 给字典打安全标记,禁止字典进行rehash
iter->d->iterators++;
else
// 记录迭代器指纹,就好比字典的md5值
// 如果遍历过程中字典有任何变动,指纹就会改变(释放iterator时检查指纹是否发生改变)
iter->fingerprint = dictFingerprint(iter->d);
}
iter->index++;
// ht[0]遍历完之后, 遍历ht[1]中的节点
if (iter->index >= (long) ht->size) {
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) {
/* 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;
}
在往字典插入新键值对的时候, 判断是否是需要动态扩容:
在添加,删除,更新的时候都会进行重散列操作(在没有安全模式迭代器时), 将部分ht[0]
中的数据迁移到ht[1]
中.
调用流程: _dictRehashStep
--> dictRehash
每次最多重散列10个槽位, rehashidx
表示槽位所在数组的索引位置.
int dictRehash(dict *d, int n) {
int empty_visits = n*10; /* Max number of empty buckets to visit. */
if (!dictIsRehashing(d)) return 0;
while(n-- && d->ht[0].used != 0) {
dictEntry *de, *nextde;
/* Note that rehashidx can't overflow as we are sure there are more
* elements because ht[0].used != 0 */
assert(d->ht[0].size > (unsigned long)d->rehashidx);
// 查找飞空槽位
while(d->ht[0].table[d->rehashidx] == NULL) {
d->rehashidx++;
if (--empty_visits == 0) return 1;
}
de = d->ht[0].table[d->rehashidx];
/* Move all the keys in this bucket from the old to the new hash HT */
// 非空槽位下链表上所有数据都要搬移
while(de) {
unsigned int h;
nextde = de->next;
/* Get the index in the new hash table */
h = dictHashKey(d, de->key) & d->ht[1].sizemask;
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;
d->rehashidx++;
}
/* Check if we already rehashed the whole table... */
// 如果重散列完成, 将ht[1]赋值给ht[0],然后resetht[1]和rehashidx
if (d->ht[0].used == 0) {
zfree(d->ht[0].table);
d->ht[0] = d->ht[1];
_dictReset(&d->ht[1]);
d->rehashidx = -1;
return 0;
}
/* More to rehash... */
return 1;
}
跋山涉水 —— 深入 Redis 字典遍历