1 /* Performs N steps of incremental rehashing. Returns 1 if there are still
2 * keys to move from the old to the new hash table, otherwise 0 is returned.
3 * Note that a rehashing step consists in moving a bucket (that may have more
4 * thank one key as we use chaining) from the old to the new hash table.
5 进行N步增量重哈希。
6 如果没有完成旧哈希表中所有值的重哈希,返回1,否则返回0。
7 每一步重哈希移动一整个哈希桶。
8 */
9 int dictRehash(dict *d, int n) {
10 if (!dictIsRehashing(d)) return 0;
11 while(n--) {
12 dictEntry *de, *nextde;
13
14 /* Check if we already rehashed the whole table... */
15 if (d->ht[0].used == 0) {
16 zfree(d->ht[0].table);
17 d->ht[0] = d->ht[1];
18 _dictReset(&d->ht[1]);
19 d->rehashidx = -1;
20 return 0;
21 }
22 /* Note that rehashidx can't overflow as we are sure there are more
23 * elements because ht[0].used != 0 */
24 while(d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++;
25 de = d->ht[0].table[d->rehashidx];
26 /* Move all the keys in this bucket from the old to the new hash HT */
27 while(de) {
28 unsigned int h;
29 nextde = de->next;
30 /* Get the index in the new hash table */
31 h = dictHashKey(d, de->key) & d->ht[1].sizemask;
32 de->next = d->ht[1].table[h];
33 d->ht[1].table[h] = de;
34 d->ht[0].used--;
35 d->ht[1].used++;
36 de = nextde;
37 }
38 d->ht[0].table[d->rehashidx] = NULL;
39 d->rehashidx++;
40 }
41 return 1;
42 }
结构体dict中的rehashidx变量表示当前需要rehash的桶的下标,也就是table的下标。如果rehashidx为-1,表示当前没有进行rehash。宏dictIsRehashing通过rehashidx判断是否在进行rehash。
dictRehash函数执行n步rehash。每步中,首先判断是否已经完成了rehash,判断的标准就是ht[0]的used为0。ht[0]的used为0说明table中所有的链表都是空的,那么所有的元素都已经移动到ht[1]中。此时,将ht[1]赋值给ht[0],清空ht[1]。将rehashidx赋值为-1表明rehash结束。如果rehash没有结束,那么,查找ht[0]中下一个非空的桶。将这个桶中的所有数据rehash到ht[1]中。
那么,什么时候开始rehash?首先说明一下rehash是干吗的。从前面的代码中可以看出,所谓的rehash,不管你分不分步,无非就是把数据从ht[0]中取出,重新计算hash值,然后放到ht[1]对应的桶中,然后把ht[1]变成ht[0]。读者们可以看看dictAdd代码,rehash的步骤和把数据放入ht[0]的过程没什么区别。这就比较蛋疼了,搞毛啊,穷折腾么?显然不是!在rehash的时候,ht[0]和ht[1]的table的长度是不一样的!ht[1]的table长度是ht[0]的table长度的2倍。数据对应与ht[1]的桶的下标和在ht[0]的桶的下标是不一样的。前面介绍的那两种哈希方法计算出来的哈希值通常会很大,要远大于table的长度。因此,这个哈希值与table长度进行与运算的时候,得到的值就不一样。说白了,在ht[0]中,桶下表是哈希值的低m位,在ht[1]中就是低m+1位,两个值自然极有可能不一样了。所以,rehash之后,ht[1]中链表的长度大大减少。随着数据的增加,ht[0]中的链表会越来越长,这会造成查找效率降低。为了降低链表的长度,要加大桶的长度。在dictAdd函数中,调用_dictKeyIndex函数。_dictKeyIndex函数查找新的key所对应的桶的下标。_dictKeyIndex函数调用_dictExpandIfNeeded函数判断是否需要扩充ht[0]的table。_dictExpandIfNeeded函数调用dictExpand函数进行实际的扩充。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
7 /* the size is invalid if it is smaller than the number of
8 * elements already inside the hashtable */
9 if (dictIsRehashing(d) || d->ht[0].used > size)
10 return DICT_ERR;
11
12 /* Allocate the new hashtable and initialize all pointers to NULL */
13 n.size = realsize;
14 n.sizemask = realsize-1;
15 n.table = zcalloc(realsize*sizeof(dictEntry*));
16 n.used = 0;
17
18 /* Is this the first initialization? If so it's not really a rehashing
19 * we just set the first hash table so that it can accept keys. */
20 if (d->ht[0].table == NULL) {
21 d->ht[0] = n;
22 return DICT_OK;
23 }
24
25 /* Prepare a second hash table for incremental rehashing */
26 d->ht[1] = n;
27 d->rehashidx = 0;
28 return DICT_OK;
29 }