找回密码
 用户注册

QQ登录

只需一步,快速开始

查看: 4971|回复: 0

Redis源代码分析之二:散列表——Dict(上)

[复制链接]
发表于 2012-2-2 19:28:33 | 显示全部楼层 |阅读模式
先介绍Redis散列表实现的几个重要数据结构:

字典项DictEntry:typedef struct dictEntry {
  1.     void *key;
  2.     void *val;
  3.     struct dictEntry *next;
  4. } dictEntry;
复制代码
字典类型DictType:typedef struct dictType {
  1.     unsigned int (*hashFunction)(const void *key);
  2.     void *(*keyDup)(void *privdata, const void *key);
  3.     void *(*valDup)(void *privdata, const void *obj);
  4.     int (*keyCompare)(void *privdata, const void *key1, const void *key2);
  5.     void (*keyDestructor)(void *privdata, void *key);
  6.     void (*valDestructor)(void *privdata, void *obj);
  7. } dictType;
复制代码
哈希表结构dictht:
/* This is our hash table structure. Every dictionary has two of this as we
  1. * implement incremental rehashing, for the old to the new table. */
  2. typedef struct dictht {
  3.     dictEntry **table;
  4.     unsigned long size;
  5.     unsigned long sizemask;
  6.     unsigned long used;
  7. } dictht;
复制代码


字典结构dict:typedef struct dict {
  1.     dictType *type;
  2.     void *privdata;
  3.     dictht ht[2];
  4.     int rehashidx; /* rehashing not in progress if rehashidx == -1 */
  5.     int iterators; /* number of iterators currently running */
  6. } dict;
复制代码

字典迭代器dictIterator:/* If safe is set to 1 this is a safe iteartor, that means, you can call
  1. * dictAdd, dictFind, and other functions against the dictionary even while
  2. * iterating. Otherwise it is a non safe iterator, and only dictNext()
  3. * should be called while iterating. */
  4. typedef struct dictIterator {
  5.     dict *d;
  6.     int table, index, safe;
  7.     dictEntry *entry, *nextEntry;
  8. } dictIterator;
复制代码
然后介绍一下字典接口定义:dict *dictCreate(dictType *type, void *privDataPtr);
  1. int dictExpand(dict *d, unsigned long size);
  2. int dictAdd(dict *d, void *key, void *val);
  3. int dictReplace(dict *d, void *key, void *val);
  4. int dictDelete(dict *d, const void *key);
  5. int dictDeleteNoFree(dict *d, const void *key);
  6. void dictRelease(dict *d);
  7. dictEntry * dictFind(dict *d, const void *key);
  8. void *dictFetchValue(dict *d, const void *key);
  9. int dictResize(dict *d);
  10. dictIterator *dictGetIterator(dict *d);
  11. dictIterator *dictGetSafeIterator(dict *d);
  12. dictEntry *dictNext(dictIterator *iter);
  13. void dictReleaseIterator(dictIterator *iter);
  14. dictEntry *dictGetRandomKey(dict *d);
  15. void dictPrintStats(dict *d);
  16. unsigned int dictGenHashFunction(const unsigned char *buf, int len);
  17. unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len);
  18. void dictEmpty(dict *d);
  19. void dictEnableResize(void);
  20. void dictDisableResize(void);
  21. int dictRehash(dict *d, int n);
  22. int dictRehashMilliseconds(dict *d, int ms);
复制代码

下面分析扩张或者创建哈希表的重要函数dictExpand:
  1. /* Expand or create the hashtable */
  2. int dictExpand(dict *d, unsigned long size)
  3. {
  4.     dictht n; /* the new hashtable */
  5.     unsigned long realsize = _dictNextPower(size);
  6.     /* the size is invalid if it is smaller than the number of
  7.      * elements already inside the hashtable */
  8.     if (dictIsRehashing(d) || d->ht[0].used > size)
  9.         return DICT_ERR;
  10.     /* Allocate the new hashtable and initialize all pointers to NULL */
  11.     n.size = realsize;
  12.     n.sizemask = realsize-1;
  13.     n.table = zcalloc(realsize*sizeof(dictEntry*));
  14.     n.used = 0;
  15.     /* Is this the first initialization? If so it's not really a rehashing
  16.      * we just set the first hash table so that it can accept keys. */
  17.     if (d->ht[0].table == NULL) {
  18.         d->ht[0] = n;
  19.         return DICT_OK;
  20.     }
  21.     /* Prepare a second hash table for incremental rehashing */
  22.     d->ht[1] = n;
  23.     d->rehashidx = 0;
  24.     return DICT_OK;
  25. }
复制代码
对于传入的参数:新哈希表的大小size,首先调用内部函数_dictNextPower(size)取得大于size的最小2次幂整数,作为哈希表大小。掩码sizemask为size二进制表示长度减一的全1表示。调用内存管理函数zcalloc分配新哈希表的内存。
接下来,函数判断这是否是哈希表的首次初始化,这通过判断字典的哈希表数组ht的首个元素的dictEntry是否为空实现,如果为空,说明是首次初始化,则将该哈希表的size设为n,直接返回DICT_OK;否则,说明这是一次rehash,那么函数将准备第二个哈希表d->ht[1],并将d的rehashidx设为0,准备进行后续的增量哈希,然后返回DICT_OK。
下面分析再哈希的实现dictRehash函数:/* Performs N steps of incremental rehashing. Returns 1 if there are still
  1. * keys to move from the old to the new hash table, otherwise 0 is returned.
  2. * Note that a rehashing step consists in moving a bucket (that may have more
  3. * thank one key as we use chaining) from the old to the new hash table. */
  4. int dictRehash(dict *d, int n) {
  5.     if (!dictIsRehashing(d)) return 0;
  6.     while(n--) {
  7.         dictEntry *de, *nextde;
  8.         /* Check if we already rehashed the whole table... */
  9.         if (d->ht[0].used == 0) {
  10.             zfree(d->ht[0].table);
  11.             d->ht[0] = d->ht[1];
  12.             _dictReset(&d->ht[1]);
  13.             d->rehashidx = -1;
  14.             return 0;
  15.         }
  16.         /* Note that rehashidx can't overflow as we are sure there are more
  17.          * elements because ht[0].used != 0 */
  18.         while(d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++;
  19.         de = d->ht[0].table[d->rehashidx];
  20.         /* Move all the keys in this bucket from the old to the new hash HT */
  21.         while(de) {
  22.             unsigned int h;
  23.             nextde = de->next;
  24.             /* Get the index in the new hash table */
  25.             h = dictHashKey(d, de->key) & d->ht[1].sizemask;
  26.             de->next = d->ht[1].table[h];
  27.             d->ht[1].table[h] = de;
  28.             d->ht[0].used--;
  29.             d->ht[1].used++;
  30.             de = nextde;
  31.         }
  32.         d->ht[0].table[d->rehashidx] = NULL;
  33.         d->rehashidx++;
  34.     }
  35.     return 1;
  36. }
复制代码
首先判断这是否是一次合法的rehash调用,通过判断(ht)->rehashidx!=-1实现。
然后,进行n步rehash。其中的每一步都重复如下步骤:
(1) 检查我们是否已经rehash了整个哈希表(此时d->ht[0].used为0),如果是,析构旧的哈希表,将d->rehashidx置为-1。
(2)  遍历哈希表d->ht[0],直到找到非空的字典项de,然后此后通过de->next继续遍历。在此之前,通过位操作dictHashKey(d, de->key) & d->ht[1].sizemask获得在新哈希表d->ht[1]中的索引h,并将该字典项复制到新哈希表d->ht[1],同时更新两个哈希表的d->ht.used计数。然后将最初de对应的rehashidx对应的字典项标记为NULL,将->rehashidx加1,然后重复(1)层的循环。




作者:Aegeaner 发表于2012-2-1 20:16:48 原文链接

您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

Archiver|手机版|小黑屋|ACE Developer ( 京ICP备06055248号 )

GMT+8, 2024-5-3 17:12 , Processed in 0.031173 second(s), 7 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表