redis的字典使用哈希表作为底层实现。
结构体定义在dict.h中。
字典的结构体定义:
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
unsigned long iterators; /* number of iterators currently running */
} dict;
ht是一个长度为2的数组,对应的是两个哈希表,一般使用使用ht[0],ht[1]主要在扩容和缩容时使用。
哈希表结构体定义:
typedef struct dictht {
//哈希表数组
dictEntry **table;
//哈希表大小
unsigned long size;
//哈希表大小的掩码,用于计算索引值
//总是等于size-1
unsigned long sizemask;
//已有节点的数量
unsigned long used;
} dictht;
table是一个数组,对应的是多个哈希表节点dictEntry
哈希表节点key/value结构体定义:
typedef struct dictEntry {
//键
void *key;
//值
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
//指向下一个哈希表节点,形成链表
struct dictEntry *next;
} dictEntry;
哈希表每个节点都保存着一个键值对,key就是键值对的键,v属性就是对应键值对的值,v可以是一个指针也可以是uint64_t,整数也可以是int64_t整数。
next是一个链表,指向着下一个哈希表节点,这个指针可以将多个哈希值相同的键值对连接在一起,以此来解决哈希冲突问题。
redis字典实现是使用链地址法,哈希算法具体方式为:
计算键key的hash值
hash = (ht)->type->hashFunction(key)
通过hash与sizemask的位运算计算出哈希table数组(桶)对应的索引
h = dictHashKey(ht, key) & ht->sizemask;
插入的时候,可能会出现键被分配到同一个哈希表数组的索引上,引发哈希冲突,解决哈希冲突的方式是每次把新增节点往单向链表的头部插入,每个节点会记录下一个节点的信息next。
每次插入的时候,会检查是否需要扩容,扩容由负载因子决定的,负载因子=哈希表已保存的节点数/哈希表大小
d->ht[0].used/d->ht[0].size
扩容和收缩通过dictExpand完成,扩容或收缩表需要把ht[0]的所有键rehash到ht[1]里面,考虑服务器性能原因rehash并不会一次性地把所有ht[0]所有键rehash到ht[1]里,而是渐进式、分多次的完成。
dict *dictCreate(dictType *type,
void *privDataPtr)
{
//分配创建字典的内存
dict *d = zmalloc(sizeof(*d));
//对字典进行初始化
_dictInit(d,type,privDataPtr);
return d;
}
int _dictInit(dict *d, dictType *type,
void *privDataPtr)
{
//初始化hash表
_dictReset(&d->ht[0]);
_dictReset(&d->ht[1]);
d->type = type;
d->privdata = privDataPtr;
d->rehashidx = -1;
d->iterators = 0;
return DICT_OK;
}
在添加、释放字典的时候会调用_dictExpandIfNeeded函数判断字典是否需要扩容和收缩,扩容为二倍扩容,具体的扩容和收缩函数为dictExpand。
static int dictExpand(dict *ht, unsigned long size) {
if (dictIsRehashing(d) || d->ht[0].used > size)
return DICT_ERR;
//新hash表
dictht n; /* the new hash table */
unsigned long realsize = _dictNextPower(size);
/* Rehashing to the same table size is not useful. */
if (realsize == d->ht[0].size) return DICT_ERR;
/* Allocate the new hash table and initialize all pointers to NULL */
n.size = realsize;
n.sizemask = realsize-1;
n.table = zcalloc(realsize*sizeof(dictEntry*));
n.used = 0;
if (d->ht[0].table == NULL) {
d->ht[0] = n;
return DICT_OK;
}
d->ht[1] = n;
d->rehashidx = 0;
return DICT_OK;
}
扩展和缩容表实际上不是一次完成的,因为考虑键值对可能非常大,如果一次完成可能会非常消耗性能。
渐进触发rehash
渐进式rehash方法在dict.c/dictRehash中
static void _dictRehashStep(dict *d) {
if (d->iterators == 0) dictRehash(d,1);
}
int dictRehash(dict *d, int n) {
int empty_visits = n*10; /* Max number of empty buckets to visit. */
if (!dictIsRehashing(d)) return 0;
//进行n步rehash
while(n-- && d->ht[0].used != 0) {
dictEntry *de, *nextde;
//防止rehashidx越界
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];
while(de) {
uint64_t h;
nextde = de->next;
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++;
}
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;
}
return 1;
}
因为rehash是渐进式的,所以也会引发问题是在删除、查找、更新等操作的时候都在两个哈希表中进行。
另外在rehash期间,新添加的字典的键值对会保存到ht[1]里面,而ht[0]则不会进行任何添加操作,保证ht[0]只减不增,随着rehash操作执行最终变为空表。
添加的方法在dict.c/dictAdd
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;
}
dictAdd会调用dictAddRaw生成一个新的哈希节点,然后调用dictSetVal去设置这个哈希节点的val值,设置哈希节点的key值是在dictAddRaw方法里。
dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
{
long index;
dictEntry *entry;
dictht *ht;
//如果正在rehash,进行渐进rehash
if (dictIsRehashing(d)) _dictRehashStep(d);
if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1)
return NULL;
ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
entry = zmalloc(sizeof(*entry));
entry->next = ht->table[index];
ht->table[index] = entry;
ht->used++;
dictSetKey(d, entry, key);
return entry;
}
查找table的索引方法是在dict.c/_dictKeyIndex方法
static long _dictKeyIndex(dict *d, const void *key, uint64_t hash, dictEntry **existing)
{
...
for (table = 0; table <= 1; table++) {
idx = hash & d->ht[table].sizemask;
//获取到哈希表节点的链表
he = d->ht[table].table[idx];
while(he) {
if (key==he->key || dictCompareKeys(d, key, he->key)) {
if (existing) *existing = he;
return -1;
}
he = he->next;
}
}
...
return idx;
}
int dictReplace(dict *d, void *key, void *val)
{
dictEntry *entry, *existing, auxentry;
entry = dictAddRaw(d,key,&existing);
if (entry) {
dictSetVal(d, entry, val);
return 1;
}
auxentry = *existing;
dictSetVal(d, existing, val);
dictFreeVal(d, &auxentry);
return 0;
}
void *dictFetchValue(dict *d, const void *key) {
dictEntry *he;
he = dictFind(d,key);
return he ? dictGetVal(he) : NULL;
}
dictEntry *dictFind(dict *d, const void *key)
{
dictEntry *he;
uint64_t h, idx, table;
if (d->ht[0].used + d->ht[1].used == 0) return NULL; /* dict is empty */
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];
while(he) {
if (key==he->key || dictCompareKeys(d, key, he->key))
return he;
he = he->next;
}
if (!dictIsRehashing(d)) return NULL;
}
return NULL;
}
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;
}
实际上遍历字典,因为最底层字典是链表存储的,所以遍历字典很简单相当于遍历字典就可以了,只是因为收缩和扩容的原因,需要遍历2个hash表。
dictEntry *dictNext(dictIterator *iter)
{
while (1) {
if (iter->entry == NULL) {
dictht *ht = &iter->d->ht[iter->table];
if (iter->index == -1 && iter->table == 0) {
if (iter->safe)
iter->d->iterators++;
else
iter->fingerprint = dictFingerprint(iter->d);
}
iter->index++;
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;
}
更多讲解,欢迎关注我的github:
go成神之路