字典,又称为符号表 (symbol table)、关联数组 (associated array)或映射 (map),是一种用于保存键值对 (key-value pair)的抽象数据结构。在 redis 中,哈希键和数据库都是通过字典作为底层实现的。
结构体
redis 中字典的结构如下:
typedef struct dictEntry {
void *key; //键
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v; //值,使用联合体,可以是指针,也可以是其他类型的值
struct dictEntry *next; //使用拉链法解决哈希冲突问题
} dictEntry;
对两个64位类型解释如下
typedef unsigned __int64 uint64_t;
typedef signed __int64 int64_t;
可以将 __int64
理解成 long long
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;
dictType
结构体为一簇操作特定类型键值对的函数,privdata
为传给这些函数的可选参数
/* This is our hash table structure. Every dictionary has two of this as we
* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht;
dictht 为哈希表结构,哈希表结构有一个 dictEntry 的双重指针(可理解成 dictEntry 指针数组),size 为哈希表大小,sizemask 为哈希表大小掩码,用于计算哈希索引值,总是等于 size-1
,used 为哈希表元素个数,即已有的节点的数量。
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
int iterators; /* number of iterators currently running */
} dict;
dict 为字典结构,包括两个哈希表ht[2], 一个用于正常使用,另一个,当需要扩大哈希表时,将ht[0]中的节点rehashed 到 ht[1] 上,然后再将 ht[1] 设置为ht[0], ht[1]置空,准备下一次 rehashed。 rehashidx 记录了 rehash 的进度,当没有做 rehash 时,它的值为 -1, 当在做 rehash 的值时,它的值表示的是当前 rehash 到了 ht[0] 中的哪一个位置了(可以理解成 ht[0].table[rehashidx])。 redis 的 rehash 是渐进式的,即通过 rehashidx 一个一个递增的形式 rehash 的。
字典的详细实现
计算哈希值和索引值
redis计算哈希值和索引值,是根据键值来计算的,先计算出哈希值,然后根据哈希值和 sizemask 计算索引值。
/* Returns the index of a free slot that can be populated with
* a hash entry for the given 'key'.
* If the key already exists, -1 is returned.
*
* Note that if we are in the process of rehashing the hash table, the
* index is always returned in the context of the second (new) hash table. */
static int _dictKeyIndex(dict *d, const void *key)
{
unsigned int h, idx, table;
dictEntry *he;
/* Expand the hash table if needed */
if (_dictExpandIfNeeded(d) == DICT_ERR) //判断是否需要扩大哈希表的大小,如果正在 rehash,返回 true
//如果used/size 大于安全阀值5时,将需要扩大哈希表大小
return -1;
/* Compute the key hash value */
h = dictHashKey(d, key); //计算哈希值
for (table = 0; table <= 1; table++) {
idx = h & d->ht[table].sizemask; //计算索引值,在哈希表中查找是否存在,存在返回-1,不存在,返回索引值
/* Search if this slot does not already contain the given key */
he = d->ht[table].table[idx];
while(he) {
if (dictCompareKeys(d, key, he->key))
return -1;
he = he->next;
}
if (!dictIsRehashing(d)) break;
}
return idx;
}
上面这个函数,为获取哈希值和索引值的过程, 首先通过 dictHashKey(d, key)
获取哈希值,这是一个宏函数
#define dictHashKey(d, key) (d)->type->hashFunction(key)
然后根据哈希值求索引值,idx = h & d->ht[table].sizemask
,如果此元素在哈希表中已存在,返回-1,不需要再次插入,否则返回一个可以用的位置。搜索哈希表时,需要在两个哈希表中都要搜索。
那么,当 _dictExpandIfNeed (d)
判断出需要扩大哈希表时,是如何扩大哈希表的呢?_dictExpandIfNeed (d)
这个函数在判断时:
1. 如果 rehashidx != -1
说明,哈希表在 rehash
&