一,前言
在介绍布谷鸟过滤器之前,我们先来了解一下布谷鸟这种动物。
布谷鸟这种鸟很有意思,布谷鸟从来不自己筑巢。它将自己的蛋产在别人的巢里,让别人来帮忙孵化。待小布谷鸟破壳而出之后,因为布谷鸟的体型相对较大,它又将养母的其它孩子(还是蛋)从巢里挤走 —— 从高空摔下夭折了。
布谷鸟过滤器起源于布谷鸟算法,而布谷鸟算法的思想源于布谷鸟的这种“鸠占鹊巢”的习性。
二,布谷鸟哈希
布谷鸟过滤器是基于布谷鸟哈希结构并在此基础上进行改良的升级版本。因此我们先来看一下布谷鸟哈希:
1. 布谷鸟哈希原理
最简单的布谷鸟哈希结构就是 1个一维数组 + 2个哈希函数,而这两个 hash 函数将新来的元素自身映射到数组的两个位置。如果两个位置中有一个位置为空,那么就可以将元素直接放进去。但是如果这两个位置都满了,它就不得不「鸠占鹊巢」,随机踢走一个,然后自己霸占了这个位置。
① 插入元素,两个位置都没有被占用
② 插入元素,两个位置只占用了一个
③插入元素,两个位置都被占用了
2. 布谷鸟哈希的问题
基于上面的3张图所演示的逻辑,我们可以从中发现:
问题:
- 数组将近满的时候,新插入元素会出现一直发生“鸠占鹊巢”的现象,可能这样几百次都没有找到合适的位置占用,这时候会严重影响插入效率。
- 出现挤兑循环问题。比如两个不同的元素,hash 之后的两个位置正好相同,这时候它们一人一个位置没有问题。但是这时候来了第三个元素,它 hash 之后的位置也和它们一样,很明显,这时候会出现挤兑的循环。
解决方法:
- 对于第一个问题:布谷鸟哈希会设置一个阈值,当连续占巢行为超出了某个阈值,就认为这个数组已经几乎满了。这时候就需要对它进行扩容,重新放置所有元素。
- 对于第二个问题:布谷鸟哈希算法对待这种挤兑循环的态度就是认为数组太拥挤了,需要扩容。(实际上不是这样)
3. 优化布谷鸟哈希
上面的布谷鸟哈希算法的平均空间利用率并不高,大概只有 50%。到了这个百分比,就会很快出现连续挤兑次数超出阈值。这样的哈希算法价值并不明显,所以需要对它进行改良。
改良的方案
① 增加 hash 函数个数,让每个元素不止有两个巢,而是三个巢、四个巢。这样可以大大降低碰撞的概率,将提高空间利用率到 95%左右。
② 在数组的每个位置上挂上多个座位,这样即使两个元素被 hash 在了同一个位置,也不必立即“鸠占鹊巢”,因为这里有多个座位,你可以随意坐一个。除非这多个座位都被占了,才需要进行挤兑。很明显这也会显著降低挤兑次数。这种方案的空间利用率只有 85%左右,但是查询效率会很高,同一个位置上的多个座位在内存空间上是连续的,可以有效提高CPU缓存命中率。
结论:所以更加高效的方案是将上面的两个改良方案融合起来,比如使用 4 个 hash 函数,每个位置上放 2 个座位。这样既可以得到时间效率,又可以得到空间效率。这样的组合甚至可以将空间利用率提到高 99%,这是非常了不起的空间效率。
三,布谷鸟过滤器
1. 布谷鸟过滤器原理
布谷鸟过滤器由一个数组(元素个数为2的n次方)和2个Hash函数组成,其中数组中每个元素存储4个指纹,数组中每个元素大小为4个字节,每一个字节是一个指纹(1字节8bit位,共256种:00000000~11111111),可以存储4个指纹。因此每个元素由两个Hash函数得到两个位置,总共有8个座位(存储8个指纹)
数组结构代码:
这样做的好处:
- 可以很大程度减少挤兑循环的情况:即使两个元素被 hash 在了同一个位置,也不必立即"鸠占鹊巢",因为这里有4个座位,你可以随意坐一个。除非这多个座位都被占了,才需要进行挤兑。这会显著降低挤兑次数。
- 同一个位置上的多个座位在内存空间上是连续的,可以有效利用 CPU 高速缓存,增加CPU缓存命中率(连续内存查找性能提升了)。
布谷鸟过滤器的插入算法:
与布谷鸟哈希逻辑一样(都是布谷鸟算法的思想),只是在算法层面有些区别:
布谷鸟过滤器删除算法:
2. 布谷鸟过滤器的问题
根据上述原理我们可以发现:
- 插入重复元素存在上限:两个Hash函数得到2个位置,每个位置存储4个指纹,因此最多插入8次重复元素,当第九次插入相同元素时就会出现挤兑循环问题
- 存在误判率:由于指纹只有256种可能,因此海量数据必然会有两个不同数据有同样的指纹,且插入位置有交集的情况。此时删除可能错删。
四,相比布隆过滤器,布谷鸟过滤器优缺点
布谷鸟过滤波器优点:
- 布谷鸟过滤器可以进行删除操作,布隆过滤器不可以
- 布谷鸟过滤器空间利用率更高,布谷鸟过滤器采用布谷鸟算法的思想“鸠占鹊巢”,而布隆过滤器存在一个bit位会被覆盖的情况。
- 布谷鸟过滤器的CPU缓存命中率更高(也就代表查询效率更高),布谷鸟过滤器采用连续内存空间存放指纹信息。
布谷鸟过滤器缺点:
- 存在误判率(删除时误删)
- 布谷鸟过滤器的插入效率低
- 插入重复元素存在上限