字典。用于保存键值对,每个键都是唯一的,dict基于哈希表算法,采用某个哈希函数从key计算得到哈希表种的位置。采用拉链法解决冲突,在装载因子(load factor)超过预定值时自动扩展内存,引发重哈希(rehashing)
redis 字典采用增量式重哈希算法(incremental rehashing),在需要扩展内存时避免一次性对所有key进行重哈希,而是将重哈希操作分散到对于dict的各个增删改查的操作中去。每次只对一小部分key进行重哈希,每次重哈希之间不影响dict的操作。从而避免了重哈希期间,单个请求的响应时间剧烈增加。
字典条目dictEntry
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry;
字典方法dictType
typedef struct dictType {
uint64_t (*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;
包含若干函数指针,用于dict调用者自定义对key和value的各种操作,包括:
hashFunction:对key进行哈希值计算的哈希算法
keyDup和valdup:对key、value拷贝,在需要的时候对key和value进行深拷贝,而不仅仅是传递对象指针
keyCompare:比较key
keyDestructor、valDestructor:对key、value析构
字典哈希表dictht
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht;
通过指针数组保存哈希桶,记录桶的个数、已使用的个数
table dictEntry指针数组:key的哈希值最终映射到数组的某个位置上(对应一个bucket).如果多个key映射到同一个位置,就发生了冲突,需要拉出一个dictEntry链表
size:dictEntry指针数组的长度,总是2的指数
sizemask:用于将哈希值映射到table的位置索引。等于(size-1)
每个key先经过hashFunction计算得到一个哈希值,然后计算哈希值&sizemask得到在table上的位置(相当于取余)
used:记录dict中现有的数据个数,used/size = 装载因子(load factor),装载因子越大,哈希值冲突概率越高
字典dict
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;
type 自定义方法:通过自定义的方式使得dict的key和value能够存储任何类型的数据
privdata 私有数据
ht 字典哈希表:只有在重哈希的过程中,ht[0]和ht[1]才都有效。而在平常情况下,只有ht[0]有效,ht[1]里面没有任何数据
rehashidx 重哈希标识:如果rehashidx = -1, 表示当前没有在重哈希过程中;否则,表示当前正在进行重哈希,且它的值记录了当前重哈希进行到哪一步
iterators 当前正在进行遍历的迭代器数量
dict - dictht - dictEntry*[] - dictEntry
创建
static dict *dictCreate(dictType *type, void *privDataPtr) 创建一个dict
static int _dictInit(dict *ht, dictType *type, void *privDataPtr) 初始化dict
查找
static dictEntry *dictFind(dict *ht, const void *key)
如果dict当前正在重哈希,那么_dictRehashStep将重哈希过程推进一步
dictHashKey根据key计算哈希值h, h &= ht sizemask
查找哈希表ht[0],遍历ht[0].table[h] dict entry链表,dictCompareKeys比较key,相等则返回
判断dict当前正在重哈希,那么继续在h[1]上查找,否则直接返回NULL
增量式重哈希
static void _dictRehashStep(dict *d)
int dictRehash(dict *d, int n) 将重哈希推进n步,每一步将ht[0]上的某一个dict entry链表上的dict entry移动到ht[1]上,根据ht[1]的sizemask重新继续算哈希值h
dictEntry *de, *nextde;
uint64_t h;
for(de = d->ht[0].table[d->rehashidx]; de != NULL; de = nextde) {
nextde = de->next;
/* de插入到ht[1].table[h]链表头 */
h = dictHashKey(d, de->key) & d->ht[1].sizemask;
de->next = d->ht[1].table[h];
d->ht[1].table[h] = de;
d->ht[1].used++;
d->ht[0].used--;
}
d->ht[0].table[d->rehashidx] = NULL;
d->rehashidx++;
rehashidx记录ht[0]当前待迁移的dict entry链表位置,如果这个位置没有dict entry,则向后遍历ht[0].table数组,直到找到有数据的位置
如果一直找不都,则最多走empty_visits(n * 10)步
如果ht[0]上的数据都被迁移到ht[1]上了(dict->ht[0].used == 0),重哈希结束(rehashidx 置为-1),ht[0] free旧数据, 指向ht[1],ht[1] _dictReset置空
插入
int dictAdd(dict *d, void *key, void *val) 添加一条记录,如果key已存在则添加失败
dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
int dictReplace(dict *d, void *key, void *val) 添加或更新一条记录
dictAddRaw
如果dict当前正在重哈希,把数据插到ht[1],否则插入到ht[0]
在对应bucket中插入数据时,总是头插,因为新数据接下来被访问的概率可能比较高,可以减少查找次数
_dictKeyIndex在dict中寻找插入位置,如果dict当前没有在重哈希,则只查找ht[0],否则查找ht[0]和ht[1]
_dictKeyIndex可能触发dict内存扩展,_dictExpandIfNeeded将哈希表长度扩展为原来2倍 dictExpand(d, d->ht[0].used*2)
_dictKeyIndex
根据key计算hash值h
遍历bucket d->ht[0].table[h], 查找已经有相同key dictCompareKeys,如果有则返回-1,如果没有则返回h
dictReplace
先尝试添加,如果失败则再次查找已存在的key,改变它的value dictSetHashVal,调用dictFreeEntryVal析构旧value
删除
static int dictDelete(dict *ht, const void *key) 查找并删除一个元素
dictEntry *dictUnlink(dict *ht, const void *key)
static dictEntry *dictGenericDelete(dict *d, const void *key, int nofree)
dictDelete
dictEntry *de, *prevde;
de = ht->table[h];
prevde = NULL;
while(de) {
if (dictCompareHashKeys(ht,key,de->key)) {
/* Unlink the element from the list */
if (prevde)
prevde->next = de->next;
else
ht->table[h] = de->next;
dictFreeEntryKey(ht,de);
dictFreeEntryVal(ht,de);
free(de);
ht->used--;
return DICT_OK;
}
prevde = de;
de = de->next;
}
dictGenericDelete
辅助dictDelete和dictUnlink的函数,它的功能是搜索并删除,要注意的是dictDelete也会触发推进一步重哈希。如果当前不在重哈希过程中,它只在ht[0]中查找要删除的key;否则ht[0]和ht[1]它都要查找。删除成功后会调用key和value的析构函数(keyDestructor和valDestructor)。