数据结构
哈希表节点
// 字典中的键值对
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