布谷鸟过滤器原理详解

布谷鸟过滤器 vs 布隆过滤器 核心原理与特性解析

与布隆过滤器一样,布谷鸟过滤器也是用来快速判断一个元素是否存在的,但是解决了布隆过滤器"无法删除"的痛点
一、底层数据结构对比

底层数据结构中,布隆过滤器采用的是一维bit数组,每个位只能存储01状态,通过若干hash函数命中多个位来判断元素是否存在,具有存在的误判风险

而布谷鸟过滤器采用的是一维桶数组,每个bucket存储元素的"指纹"fingerprint,也就是将元素值做了一边hash,是一个8位2进制数,而且只有两个hash函数,同样也具有存在误判风险
二、布谷鸟过滤器核心操作

  1. 插入数据:"双哈希找巢,指纹占位置" 对要存入的任意值(比如字符串 "user123"):

① 生成指纹:先通过哈希函数把值转化为短且固定长度的 "指纹"(比如把 "user123" 哈希成 8 位二进制数 "10110010")------ 指纹是值的 "精简版",比原值小得多,目的是节省内存;

② 双哈希定位桶:用两个独立的哈希函数(Hash1、Hash2),计算出这个值在 "桶数组" 里的两个候选桶位置(比如 Hash1 算出来是第 10 号桶,Hash2 算出来是第 25 号桶);

③ 存入规则:把指纹放进任意一个候选桶(只要桶里还有空位);如果两个桶都满了,就 "踢走" 其中一个桶里已有的指纹(类似布谷鸟占别的鸟的巢),被踢走的指纹再用它自己的另一个候选桶位置重新插入,直到找到空位(或重试上限,说明过滤器满了)。

  1. 查询数据:"双桶找指纹,有则判存在"(对应官方 "membership query") 要判断 "值 x 是否存在":

① 生成 x 的指纹(和插入时的规则一致);

② 用 Hash1、Hash2 算出 x 的两个候选桶;

③ 只要其中一个桶里能找到和 x 完全匹配的指纹,就返回 "存在";两个桶都没有,就返回 "不存在"。 关键特性:只有 "假阳性",没有 "假阴性" 和布隆过滤器一样,布谷鸟过滤器的判断结果规则是: 说 "不存在":100% 准确(绝对不存在); 说 "存在":可能误判(实际不存在,但指纹巧合匹配)------ 这就是 "假阳性(false positive)"。

  1. 删除数据: "生成待删除元素指纹, 双哈希找巢, 找到两个候选桶, 桶中找到任意相同指纹,删除一个即可"

为什么传统布隆过滤器无法删除呢? 因为布隆过滤器依靠bit数组hash命中,多个元素可能命中相同位置,那么这个位置实际包含了两个元素存在的信息,是"共享"的,删除一个元素,需要把对应位置置为0,就会影响到其他元素的判断

而布谷鸟过滤器不同,它使用的是桶数组,就算两个元素恰好计算的两个候选桶是相同的,恰好计算出的指纹也是相同的,且恰好存放指纹的实际桶也是同一个,也没关系,直接往桶里塞

因为桶是可以存放多个的,存放多个相同的指纹也没关系,天然支持存放相同指纹

删除的时候,直接找到候选桶,删除对应的一个指纹就行了

但也正因如此,只删除一个对应指纹,再去判断此元素是否存在时,有其他元素恰好指纹是相同的,且存于同一个桶中,这样可能会存在误判,官方论文中称为"假阳性"
三、误判率:指纹长度的核心影响

官方明确 "fingerprint size will directly determine the false positive rate",意思是 指纹是值的 "精简标识",长度越短(比如 4 位),不同值生成相同指纹的概率越高,误判率就越高; 指纹越长(比如 16 位),相同指纹的概率越低,误判率就越低,但每个元素生成的指纹越长, 占用的内存也越多。

举个实际数值(Redis 中常用配置):

8 位指纹:误判率≈1/256(0.39%),适合对误判率要求不高、追求内存极致节省的场景;

16 位指纹:误判率≈1/65536(0.0015%),适合对误判率敏感的场景(比如黑名单校验)。
四、Redis 中布谷鸟过滤器的优劣势

支持删除数据:布隆过滤器的位数组一旦置 1 无法回退,无法删除;布谷鸟过滤器只需删除对应桶里的指纹即可,适合需要动态更新的场景(比如黑名单新增 / 删除);

空间效率更高:指纹是紧凑存储的,相比布隆过滤器的稀疏位数组(存在大量浪费) 相同误判率下,布谷鸟过滤器的内存占用约为布隆过滤器的 1/2~2/3;

哈希函数更少:固定 2 个哈希函数,计算开销比布隆过滤器(3-5 个)更低。

性能: 布谷鸟过滤器只需要计算一次找到hash桶,且各个桶的位置较为集中,内存上较为靠近,CPU的缓存命中率更高,查询速度快于布隆过滤器

劣势:

在接近满时,布谷鸟过滤器插入元素可能会频繁占巢,导致后续指纹不得不移动自己的位置,导致插入性能大幅下降(由于布谷鸟过滤器的"占巢"特点,元素越多,越趋向于满时,插入数据的性能会越来越低,因为插入一个就要占别人的巢,别人又要去找另一个巢,重复如此,性能会越来越低)

而且布谷鸟过滤器的容量大小必须为2的幂

可惜的是现在的具体实现并不多,GitHub上只有零星几个Go,C++,Java的实现
五、核心总结

布谷鸟过滤器底层是桶数组 + 指纹,靠 2 个哈希函数定位桶,不同于布隆过滤器的 "位数组 + 多哈希";

查询逻辑:双桶找匹配指纹,有则判存在,无则判不存在(仅可能假阳性);

误判率核心:指纹长度越短,误判率越高,内存占用越少,可按需平衡;

核心优势:支持删除、内存效率高,是 Redis 中布隆过滤器的升级版, 但是插入性能略逊于传统布隆过滤器

官方文档: https://redis.io/docs/latest/develop/data-types/probabilistic/cuckoo-filter/