布谷鸟哈希 & 布谷鸟过滤器的原理

引言

来填上篇文章《一文搞懂布隆过滤器原理》中的坑了,这篇博客我们来详细介绍一下布谷鸟哈希(Cuckoo Hash)和布谷鸟过滤器(Cuckoo Filter)的原理。
这里给出了两篇论文的链接:

布谷鸟哈希

为什么布谷鸟哈希起这个名字呢?这是因为布谷鸟生性狡猾又贪婪,它不肯自己筑巢,会把蛋下到别的鸟巢中,而且它的幼鸟也继承它的坏品德,它们通常会比正常的鸟早出生,在出生后幼鸟会将其他鸟蛋kick out,也就是踢出鸟巢,从而能独享"养父母"的食物。
布谷鸟哈希的算法就借鉴于此,当发生哈希碰撞的时候,new element会将old element踢走(kick out),不过被踢走的元素要比鸟蛋幸运,它们还有一个备用位置,当备用位置有元素时,也会将其kick out,如此循环往复,直到被踢的次数达到一个阈值,才确认哈希表已满,并执行reHash操作。
布谷鸟哈希的算法流程比较简单,如下:

准备两个哈希表T1、T2
准备两个哈希函数hash1、hash2

开始:
1.对于元素element,用hash1计算它的哈希值h1,如果哈希表T1的h1位置为空则直接放进去并结束,否则转2;
2.用hash2计算它的哈希值h2,如果哈希表T2的该位置为空则直接放进去并结束,否则转3;
3.两个位置都不为空,那么任选一个位置,将原来的元素kick out,自己插入到该位置;
4.被kick out的元素优先看自己的备用位置是否为空,为空则插入,否则再kick out其它元素;
结束;

这里也给出论文种的伪代码:
image.png
这种能放则放,放不下就踢, 循环往复的方式肯定会有一个极限情况,就比如说这样:
image.png
在这种情况下说明布谷鸟hash已经到了极限,我们需要扩容了。这也就是上面伪代码种设置MaxLoop的原因,就是为了避免相互kick out的次数太多而设置的一个阈值。
在这里也给出了一个可视化的网站,可以直观感受一下。
布谷鸟哈希就是这么一回事,还是比较简单的。

布谷鸟过滤器

布谷鸟过滤器的论文标题《Cuckoo Filter:Practically Better Than Bloom》就直接对标布隆过滤器。我们知道布隆过滤器的一大问题就是不能删除已经存在的数据,如果使用布隆过滤器的变种比如Counting Bloom Filter,支持了删除,但是空间却被撑大了3~4倍。
而布谷鸟过滤器就可以在空间或性能没有更高的开销的情况下支持元素的删除。它有以下优点:

  • 支持动态的新增和删除元素;
  • 提供了比布隆过滤器更高的查找性能,比如在接近满的情况下(空间利用率到95%);
  • 如果要求错误率小于3%,比布隆过滤器占用的空间更小;

作为过滤器,提供的API无非是插入、删除、查询。

原理

我们先看插入。相比于布谷鸟哈希的插入,布谷鸟过滤器的hash函数有所不同。来对比一下:

布谷鸟哈希:
    h1 = hash1(element) % L
    h2 = hash2(element) %L

布谷鸟过滤器:
    h1 = hash(element)
    h2 = h1⊕hash(element's fingerprint)

我们可以看到,在计算h2的时候,对元素element的fingerprint(指纹)进行了一个hash计算。
上面算法中的异或运算确保了一个重要的性质:位置h2可以通过位置h1和h1中存储的指纹计算出来,就是说因为有了异或,两个位置具有对偶性。
只要确保了hash(element’s fingerprint)!=0,那么就可以确保h1 != h2,也就是确保不会自己踢自己的情况。
这里解答几个问题:
Q1:为什么要对指纹进行一个hash计算后,再进行异或运算呢?
论文中给出了一个反证法:如果不进行hash计算,假设指纹长度是8bit,那么其对偶位置算出来,距离最远的位置才256(因为对于8bit的指纹,异或运算只可能改变h1的低8位,改变不了高位)。所以用hash(指纹)⊕h1相比于指纹⊕h1,可以确保h2会定位到哈希表中完全不同的存储桶中,从而减少的哈希冲突。
Q2:为什么布谷鸟过滤器没有对数组长度取模?
这里也是布谷鸟过滤器的一大限制,它强制我们的数组长度为2的幂次,因为只有这样我们做异或运算的时候才可以保证计算出来的下标一定落在数组中。
Q3:指纹具体是什么呢?
"指纹"就是插入元素进行哈希计算后的几个bit位,布谷鸟过滤器中存储的就是元素的指纹。
做查询的时候,就是看对应的位置上有没有对应的指纹信息。
image.png
删除也是抹除对应的指纹信息:
image.png
因为我们是对元素进行哈希计算得到指纹的,且这个指纹只有几个bit位,那么必然会导致指纹相同的情况,也就出现的误判。
这也印证了,作为过滤器,我们没必要存储原数据,只需要存储几个bit,这大大提高了空间效率。
那么能不能让空间利用率再高一点呢?
论文里这样说:
image.png
前面的(a)、(b)很简单,还是两个 hash 函数,但是没有用两个数组来存数据,就是基于一维数组的布谷鸟 hash ,核心还是踢来踢去,不多说了。重点在于©,对数组进行了展开,从一维变成了二维。每一个下标,可以放 4 个元素了。这样一个小小的转变,空间利用率从 50% 直接到了 98%。

缺点

布谷鸟过滤器会对重复的数据进行限制:如果需要布谷鸟过滤器支持删除,它必须知道一个数据插入过多少次。不能让同一个数据插入 kb+1 次。其中 k 是 hash 函数的个数,b 是一个下标的位置能放几个元素。
比如 2 个 hash 函数,一个二维数组,它的每个下标最多可以插入 4 个元素。那么对于同一个元素,最多支持插入 8 次。
比如:
image.png
why 已经插入了 8 次了,如果再次插入一个 why,则会出现循环踢出的问题,直到最大循环次数,然后返回一个 false。怎么避免这个问题呢?
我们维护一个记录表,记录每个元素插入的次数就行了。虽然逻辑简单,但是架不住数据量大呀。如果你要用布谷鸟过滤器的删除操作,那么这份难受,你不得不承受。

  • 33
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值