Redis基本数据结构-字典
1、字典概念
Redis是一个键值对数据库,在很多地方用到字典。Redis字典的实现采用的是比较经典的哈希表方式实现的。貌似跟memcached的方法有点像,很久之前看过部分memcached,现在忘得差不多了。Redis的字典定义如下:
-
-
-
-
-
- typedef struct dict {
-
-
- dictType *type;
-
-
- void *privdata;
-
-
- dictht ht[2];
-
-
- int rehashidx;
-
-
- int iterators;
-
- } dict;
-
-
-
-
-
- typedef struct dictht {
-
-
- dictEntry **table;
-
-
- unsigned long size;
-
-
- unsigned long sizemask;
-
-
- unsigned long used;
-
- } dictht;
-
-
-
-
- typedef struct dictEntry {
-
-
- void *key;
-
-
- union {
- void *val;
- uint64_t u64;
- int64_t s64;
- } v;
-
-
- struct dictEntry *next;
- } dictEntry;
使用2个哈希表,这是为了后面要说到的扩容准备的。每个哈希表如果有冲突,则采用链表方式解决。哈希表节点的指针数组为table字段,注意这是个两级指针,我习惯这样看dictEntry *table[],即table数组的每个值都是一个指向dictEntry结构体的指针。其他三个字段就是指针数组大小、长度掩码以及当前节点数。比如长度为4的话,掩码就是3。哈希表节点则是一个简单的单项链表,存储有键、值以及后继节点的指针。当不同的键哈希后的值发生冲突时,采用链表连接。
2、相关函数
2.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)
- {
-
- _dictReset(&d->ht[0]);
-
-
- _dictReset(&d->ht[1]);
-
-
- d->type = type;
- d->privdata = privDataPtr;
- d->rehashidx = -1;
- d->iterators = 0;
-
- return DICT_OK;
- }
-
-
-
-
-
-
- static void _dictReset(dictht *ht)
- {
- ht->table = NULL;
- ht->size = 0;
- ht->sizemask = 0;
- ht->used = 0;
- }
初始化过程需要注意的是type
变量的设置,
dictType
结构体类型的
type
是涉及到字典存储对象的一系列的操作方法,有点多态的味道,不同的对象赋值的
type
不同。
2.2)添加键值对到字典
-
-
-
-
-
- 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);
-
-
-
- if ((index = _dictKeyIndex(d, key)) == -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;
- }
-
添加键值对到字典主要的执行方法在dictAddRaw函数。首先会判断字典当前是否正在rehash,如果是,则调用_dictRehashStep(d)方法rehash哈希表中table数组的一个桶中元素。Redis的字典采用的是渐进式rehash方法,即在扩展哈希表的时候不会一次性将所有的元素都从老的哈希表移重新哈希到新的哈希表中,因为这样会使得本次操作延迟过长。
rehash标识是字典中的rehashidx变量,初始为-1表示没有rehash,而随着rehash的进行,这个值会设定为当前哈希到的table项。在每次扩容哈希表时,就会设置rehashidx标识。而从dictAddRaw方法看到,在新增键值对到字典时,首先要调用dictKeyIndex(d, key)方法根据key计算新增键值对的索引,即应该是table中的第几项,然后根据是否在rehash将该元素添加到正确的哈希表中,并更新字典的节点数目。最后设置key对应的value是在dictSetVal(d, entry, val)中完成。
在根据key计算索引的方法dictKeyIndex(d, key)中,首先会判断哈希表是否需要扩容:1)如果是第一次添加元素,则需要创建一个新的哈希表,大小设置为4,并设置rehashidx变量为0。 2)如果使用的节点数大于table的大小而且dict_can_resize 为真(或者已用节点数除以哈希表大小之比大于dict_force_resize_ratio),则也需要扩容哈希表为当前使用节点数的2倍大小。(注:如果哈希表本身在rehash过程中,扩容函数会直接返回,不会执行扩容)。然后查找字典的两个哈希表查找key(如果哈希表此时没有rehash,则只需要查找ht[0].table对应的项即可),如果已经存在,则返回-1,否则返回key在table中的索引值。
渐进式rehash是在扩容后下次dictAdd的时候开始执行,每次移动table中的一个项的元素到新的哈希表中。如第一次移动ht[0].table[0],第二次ht[0].table[1]中的所有元素,其实每一个项都是一个链表。如果有安全迭代器,则是不能够rehash 的。渐进式rehash跟memcached很类似的说。
-
-
-
-
-
-
-
-
-
-
-
- int dictRehash(dict *d, int n) {
- if (!dictIsRehashing(d)) return 0;
-
- while(n--) {
- dictEntry *de, *nextde;
-
-
-
- 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;
- }
-
-
-
- assert(d->ht[0].size > (unsigned)d->rehashidx);
-
- while(d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++;
-
- de = d->ht[0].table[d->rehashidx];
-
-
-
- while(de) {
- unsigned int 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++;
- }
-
-
- return 1;
- }
3)获取元素值
-
-
-
-
-
-
-
-
- void *dictFetchValue(dict *d, const void *key) {
- dictEntry *he;
-
- he = dictFind(d,key);
-
- return he ? dictGetVal(he) : NULL;
- }
首先调用dictFind函数查找字典key是否存在,存在则调用dictGetVal函数获取值。判断key是否存在是通过dictCompareKeys函数判定,而该函数在dictType结构体中赋值。如果是rehash状态,则需要查找两个哈希表并返回key对应的dictEntry项。
dictGetVal函数则是返回dictEntry项的val项即可。
3、其他
字典还有其他的函数,暂时就不分析代码了,后面有用到再细看。
4、参考资料
http://www.redisbook.com/en/latest/internal-datastruct/dict.html