找回密码
 用户注册

QQ登录

只需一步,快速开始

查看: 3920|回复: 0

[原]Redis源代码分析之三:散列表——Dict(下)

[复制链接]
发表于 2012-2-4 21:54:29 | 显示全部楼层 |阅读模式
下面分析散列表常见操作,如插入、删除、替换等。
散列表插入函数dictAdd实现如下:/* Add an element to the target hash table */
  1. int dictAdd(dict *d, void *key, void *val)
  2. {
  3.     int index;
  4.     dictEntry *entry;
  5.     dictht *ht;
  6.     if (dictIsRehashing(d)) _dictRehashStep(d);
  7.     /* Get the index of the new element, or -1 if
  8.      * the element already exists. */
  9.     if ((index = _dictKeyIndex(d, key)) == -1)
  10.         return DICT_ERR;
  11.     /* Allocates the memory and stores key */
  12.     ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
  13.     entry = zmalloc(sizeof(*entry));
  14.     entry->next = ht->table[index];
  15.     ht->table[index] = entry;
  16.     ht->used++;
  17.     /* Set the hash entry fields. */
  18.     dictSetHashKey(d, entry, key);
  19.     dictSetHashVal(d, entry, val);
  20.     return DICT_OK;
  21. }
复制代码
如果散列表正在进行再哈希,那么调用_dictRehashStep()函数执行一步再哈希,以从原哈希表H1迁移到新哈希表H2。
如果该键的索引已经存在,返回错误。
下面为新项分配内存,如果进行了rehash,分配在ht[1],否则分配在ht[0]。
最后调用dictSetHashKey和dictSetHashVal设置键和值。这两个宏的定义如下:#define dictSetHashKey(d, entry, _key_) do { \
  1.     if ((d)->type->keyDup) \
  2.         entry->key = (d)->type->keyDup((d)->privdata, _key_); \
  3.     else \
  4.         entry->key = (_key_); \
  5. } while(0)
  6. #define dictSetHashVal(d, entry, _val_) do { \
  7.     if ((d)->type->valDup) \
  8.         entry->val = (d)->type->valDup((d)->privdata, _val_); \
  9.     else \
  10.         entry->val = (_val_); \
  11. } while(0)
复制代码
这里调用了DictType中自定义的键值复制函数。
替换一个键值对的操作函数如下:/* Add an element, discarding the old if the key already exists.
  1. * Return 1 if the key was added from scratch, 0 if there was already an
  2. * element with such key and dictReplace() just performed a value update
  3. * operation. */
  4. int dictReplace(dict *d, void *key, void *val)
  5. {
  6.     dictEntry *entry, auxentry;
  7.     /* Try to add the element. If the key
  8.      * does not exists dictAdd will suceed. */
  9.     if (dictAdd(d, key, val) == DICT_OK)
  10.         return 1;
  11.     /* It already exists, get the entry */
  12.     entry = dictFind(d, key);
  13.     /* Free the old value and set the new one */
  14.     /* Set the new value and free the old one. Note that it is important
  15.      * to do that in this order, as the value may just be exactly the same
  16.      * as the previous one. In this context, think to reference counting,
  17.      * you want to increment (set), and then decrement (free), and not the
  18.      * reverse. */
  19.     auxentry = *entry;
  20.     dictSetHashVal(d, entry, val);
  21.     dictFreeEntryVal(d, &auxentry);
  22.     return 0;
  23. }
复制代码

首先尝试添加新项,如果原来不存在此项,那么就相当于用新项替换了空想,直接返回成功。
如果原来已经存在了此键的项,那么首先用dictFind查找出此项,然后设置新值,释放原项。注意这里的顺序,因为新值可能和旧值完全一样,如果先释放原项可能会使引用计数不合法。
上面提到的dictFind查找函数实现如下:dictEntry *dictFind(dict *d, const void *key)
  1. {
  2.     dictEntry *he;
  3.     unsigned int h, idx, table;
  4.     if (d->ht[0].size == 0) return NULL; /* We don't have a table at all */
  5.     if (dictIsRehashing(d)) _dictRehashStep(d);
  6.     h = dictHashKey(d, key);
  7.     for (table = 0; table <= 1; table++) {
  8.         idx = h & d->ht[table].sizemask;
  9.         he = d->ht[table].table[idx];
  10.         while(he) {
  11.             if (dictCompareHashKeys(d, key, he->key))
  12.                 return he;
  13.             he = he->next;
  14.         }
  15.         if (!dictIsRehashing(d)) return NULL;
  16.     }
  17.     return NULL;
  18. }
复制代码
我们看到,首先调用一次哈希函数得到该键的索引h,然后在两个哈希表ht[0]、ht[1]中按链表查找。


最后,我们分析删除哈希表中一项的函数dictGenericDelete:/* Search and remove an element */
  1. static int dictGenericDelete(dict *d, const void *key, int nofree)
  2. {
  3.     unsigned int h, idx;
  4.     dictEntry *he, *prevHe;
  5.     int table;
  6.     if (d->ht[0].size == 0) return DICT_ERR; /* d->ht[0].table is NULL */
  7.     if (dictIsRehashing(d)) _dictRehashStep(d);
  8.     h = dictHashKey(d, key);
  9.     for (table = 0; table <= 1; table++) {
  10.         idx = h & d->ht[table].sizemask;
  11.         he = d->ht[table].table[idx];
  12.         prevHe = NULL;
  13.         while(he) {
  14.             if (dictCompareHashKeys(d, key, he->key)) {
  15.                 /* Unlink the element from the list */
  16.                 if (prevHe)
  17.                     prevHe->next = he->next;
  18.                 else
  19.                     d->ht[table].table[idx] = he->next;
  20.                 if (!nofree) {
  21.                     dictFreeEntryKey(d, he);
  22.                     dictFreeEntryVal(d, he);
  23.                 }
  24.                 zfree(he);
  25.                 d->ht[table].used--;
  26.                 return DICT_OK;
  27.             }
  28.             prevHe = he;
  29.             he = he->next;
  30.         }
  31.         if (!dictIsRehashing(d)) break;
  32.     }
  33.     return DICT_ERR; /* not found */
  34. }
复制代码

查找要删除的项部分和dictFind函数相同,只是遍历链表的时候增加一个指针prevHe,即要找项的前一个项的指针。如果prevHe为空,说明He为链表第一项,那么简单地将其从链表头部摘下即可。否则,将索引指针指向新项。如果需要析构键、值自身的内存,则调用相关析构函数释放键、值的内存。最后释放项he的内存,更新引用计数,返回执行成功。

作者:Aegeaner 发表于2012-2-4 11:48:44 原文链接


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

本版积分规则

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

GMT+8, 2024-5-3 14:13 , Processed in 0.015928 second(s), 7 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

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