键值对的高效插入与查询-hash-哈希

来源:互联网 发布:电子商务软件教材 编辑:程序博客网 时间:2024/05/16 16:17

今天看了有关hash方面的源码,以及上网搜索了一些百家之谈,我也说一下心得体会。


由于开发是C语言,所以想c++的vector,map等用不到,说白了就是要用C语言构建类似vector的东西。

在我看来,今天说的哈希结构非常像vector,下面来说一下:

我读的代码是  redis 源码里面的dict.c dict.h

这个dict专门用于存储键值对的东西,并不局限于字符串,还可以是别的类型。下面说一下dict实现的要点部分。


1. dict 是可伸缩的,初始化的时候,dict只有 4个桶(redis初始化为4),每个桶里面有一条锁链,链子上的每个 结 就是 键值对。

2.数据如何插入、修改? 首先一个键值对要插入,先用一个hash算法,算出键的hash,然后拿这个hash跟 当前桶的个数-1(也就是4-1)相与,结果就是它要进入的桶的编号,编号找到了,然后遍历一遍这个桶里面的锁链,如果键已经存在,则把值进行替换,这就是修改操作。如果没有找到,则将新的键值对插入到锁链的头部。

2.删除操作呢? 与插入雷同,先hash,再确定桶编号,再遍历锁链,找到后 断链删除。

3.查询? 不说了,你肯定会了。


OK,到这里为止,已经全部讲完了,但是有个问题,加入插入了上百万键值对,就4个桶,会导致什么?? 桶里面的链子太长了每个链子长度都在几十万,导致增删改查都极其缓慢。

所以要引入一个机制,如何避免这个问题。下面还是拿redis的解决方案。

1.设定一个比例,叫radio,比如等于5(redis默认值)

2.假如目前有4个桶,举个栗子 , 但是插入了 23个键值对,理论上来说,这23个键值对会均匀分布到4个桶中,毕竟hash算法产生的值可以看成随机值,与(4-1)做与操作,得出的值0,1,2,3,的概率差不多。   这个radio这时就起作用了,  用 23/4 = 5.X ,这个值大于了 5 ,所以触发扩容操作也就是 所谓的 expand操作,扩容很凶残, 直接就是 当前桶数*2 ,所以这时桶变成了8个。

3.新桶有了,该把旧桶的数据转移到新桶中,怎么转移呢?   遍历旧桶中的每个锁链,取出每个键值对,然后重新算hash,算完就用新的hash 与  (8-1)相与,这时得到新的桶编号,找到了自己的新桶,然后扣在新桶的锁链上。这个过程就叫 rehash。


相信上面的举例解释够白话了,不可能听不懂。归纳一下,hash做的dict结构 采用 数组与链表两种常见结构,数组就是一个个的桶,链表就是桶中的锁链。通过与操作可以快速算出桶编号,通过  bucket【i】可以瞬间定位桶地址,找到了桶,然后通过一个radio阈值,确保锁链不会太长,所以会很快的完成查询功能。


上面讲的是原理,其实真是情况跟上面不一样,rehash操作是个费时操作,所以如果要插入一个键值对,引发扩容,进而引发rehash,会导致本次插入耗时过长,所以需要采取一些措施。

1. 两个桶数组,一个桶做缓冲用。

2.rehash操作分批进行,比如一次移一个桶或者几个桶,这些还是自己去看源码吧。