字典的定义在dict.h/dict中给出了,如下:
typedef struct dict { dictType *type; void *privdata; dictht ht[2]; long rehashidx; /* rehashing not in progress if rehashidx == -1 */ int iterators; /* number of iterators currently running */ } dict;
typedef struct dictht { dictEntry **table; unsigned long size; unsigned long sizemask; unsigned long used; } dictht;
下面是一个哈希表节点,每个dictEntry结构都保持着一个键值对,其中next指针可以将多个哈希值相同的键值对连接在一起,一次来解决键冲突的问题(这里可以引申出哈希函数以及哈希冲突解决方案,Redis中使用的解决方案是链地址法,就是,如果多个值通过哈希函数得到的哈希值是相同的,那么就链接到这个地址后,还有一种解决哈希冲突的方案,就是寻地址法,就是当出现哈希冲突的时候,对键值对在进行一个哈希函数,得到一个没有被占用的地址为止,这两种方案各有利弊,链地址法可能会退化成一个链表,寻地址法可能在后期插入时,全是冲突)
typedef struct dictEntry { void *key; union { void *val; uint64_t u64; int64_t s64; double d; } v; struct dictEntry *next; } dictEntry;
随着操作的不断执行,一个哈希表中保存的键值对会越来越多或者是越来越少,哈希表中键值对数量过多或者过少都是不好的,过多,就会相当于是多个链表,过少也不好,查找的命中率也会很低,将哈希表的负载因子(used/size)维持在一个范围之类是最好的,所以,当哈希表的数量过大或者过小的时候,程序会对哈希表进行扩展或者收缩,
扩展好理解,如果size=4 ,但是used=8,相当于每个键的后面都有个链,这样查找起来是费劲的,这个时候可以通过Rehash来进行完成,注意dict数据结构中的那个
dictht ht[2],这里是两个dictht,其中ht[1]是空闲的,在进行扩展的时候现将ht[1]扩展成ht[0]的两倍,然后将ht[0]中的键值对一个一个哈希到ht[1]中去,最后将ht[1]设置为ht[0]
这里需要注意的是rehash的时机,一般是负载因子大于5的时候扩展,负载因子小于0.1的时候收缩,还有一个问题是字典中有个属性是rehashidx,这个属性标志rehash的状态,如果是0,表示rehash正式开始,然后没rehash一个键值对,就将这个值加一,当ht[0]的值全部被转移到ht[1]的时候,就将这个值设置成-1,表示rehash操作完成。
其实还有很多要说的,比如渐进式rehash,渐进式就说说rehash过程不是一次性完成的,而是分多次,渐进式完成的,在rehash过程中,所有的删除,查找,更新都会在两个哈希表中进行,例如,如果查找一个元素,ht[0]中没有,那么就去ht[1]中查找,新添加的一律都是添加到ht[1]中,ht[0]中不再进行任何添加操作
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文地址:http://blog.csdn.net/zhaozonglu/article/details/46870353