Hash表的扩容(转载)

Hash表(Hash Table)

 

     hash表实际上由size个的桶组成一个桶数组table[0...size-1] 。当一个对象经过哈希之后,得到一个相应的value , 于是我们把这个对象放到桶table[ value ]中。当一个桶中有多个对象时,我们把桶中的对象组织成为一个链表。这在冲突处理上称之为拉链法。

 

负载因子(load factor)

 

     假设一个hash表中桶的个数为 size , 存储的元素个数为used .则我们称 used / size 为负载因子loadFactor . 一般的情况下,当loadFactor<=1时,hash表查找的期望复杂度为O(1). 因此,每次往hash表中添加元素时,我们必须保证是在loadFactor <1的情况下,才能够添加。

 

容量扩张(Expand)& 分摊转移

 

      当我们添加一个新元素时,一旦loadFactor大于等于1了,我们不能单纯的往hash表里边添加元素。因为添加完之后,loadFactor将大于1,这样也就不能保证查找的期望时间复杂度为常数级了。这时,我们应该对桶数组进行一次容量扩张,让size增大 。这样就能保证添加元素后 used / size 仍然小于等于1 , 从而保证查找的期望时间复杂度为O(1).但是,如何进行容量扩张呢? C++中的vector的容量扩张是一种好方法。于是有了如下思路 : Hash表中每次发现loadFactor==1时,就开辟一个原来桶数组的两倍空间(称为新桶数组),然后把原来的桶数组中元素全部转移过来到新的桶数组中。注意这里转移是需要元素一个个重新哈希到新桶中的,原因后面会讲到。

      这种方法的缺点是,容量扩张是一次完成的,期间要花很长时间一次转移hash表中的所有元素。这样在hash表中loadFactor==1时,往里边插入一个元素将会等候很长的时间。
    redis中的dict.c中的设计思路是用两个hash表来进行进行扩容和转移的工作:当从第一个hash表的loadFactor=1时,如果要往字典里插入一个元素,首先为第二个hash表开辟2倍第一个hash表的容量,同时将第一个hash表的一个非空桶中元素全部转移到第二个hash表中,然后把待插入元素存储到第二个hash表里。继续往字典里插入第二个元素,又会将第一个hash表的一个非空桶中元素全部转移到第二个hash表中,然后把元素存储到第二个hash表里……直到第一个hash表为空。

      这种策略就把第一个hash表所有元素的转移分摊为多次转移,而且每次转移的期望时间复杂度为O(1)。这样就不会出现某一次往字典中插入元素要等候很长时间的情况了。

为了更深入的理解这个过程,先看看在dict.h中的两个结构体:

typedef  struct dictht {
    dictEntry **table;
    unsigned  long size;
    unsigned  long sizemask;
   
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值