布谷鸟过滤器 (Cuckoo Filter)

一、简述

布谷鸟过滤器(Cuckoo Filter)是布隆过滤器的一种改进版本,由Bin Fan等人在2014年提出。它通过使用布谷鸟哈希(Cuckoo Hashing)来解决布隆过滤器不支持删除操作的问题。


布谷鸟哈希

布谷鸟哈希是2001年由Rasmus PaghFlemming Friche Rodler提出。本质上来说它为解决哈希冲突提供了另一种策略,利用较少计算换取了较大空间。它具有占用空间小、查询迅速等特性。名称源于采取了一种和布谷鸟一样的养娃方法。

布谷鸟交配后,雌性布谷鸟就准备产蛋了,但它却不会自己筑巢。它会来到像知更鸟、刺嘴莺等那些比它小的鸟类的巢中,移走原来的那窝蛋中的一个,用自己的蛋来取而代之。相对于它的体形来说,它的蛋是偏小的,而且蛋上的斑纹同它混入的其他鸟的蛋也非常相似,所以不易被分辨出来。如果不是这样,它的蛋肯定会被扔出去。

是一种鸠占鹊巢的策略,最原始的布谷鸟哈希方法是使用两个哈希函数对一个key进行哈希,得到桶中的两个位置,此时

  • 如果两个位置都为为空则将key随机存入其中一个位置
  • 如果只有一个位置为空则存入为空的位置
  • 如果都不为空,则随机踢出一个元素,踢出的元素再重新计算哈希找到相应的位置

当然假如存在绝对的空间不足,那老是踢出也不是办法,所以一般会设置一个踢出阈值,如果在某次插入行为过程中连续踢出超过阈值,则进行扩容。


优点:

支持删除操作:布谷鸟过滤器支持删除操作,且不会影响其他元素的判断。
空间效率高:与布隆过滤器相比,布谷鸟过滤器在相同误判率下,空间利用率更高。
查询速度快:查询时间复杂度为O(1)

缺点:

实现复杂:布谷鸟过滤器的实现比布隆过滤器复杂,尤其是在处理哈希冲突时。
插入性能可能下降:在高负载情况下,插入操作可能会因为频繁的“踢出”操作而性能下降。

二、应用场景

缓存系统:与布隆过滤器类似,但支持删除操作,适用于需要动态更新缓存的场景。
数据库查询优化:支持删除操作,适用于需要频繁更新数据的场景。
分布式系统:用于快速判断数据是否在分布式系统中存在。

三、工作原理

  1. 初始化:创建一个包含多个桶的数组,每个桶可以存储多个指纹(fingerprint)。
  2. 添加元素:使用两个哈希函数计算元素的两个候选桶,并将元素的指纹存储到其中一个桶中。如果两个桶都满了,则进行“踢出”操作,将原有元素踢出并重新插入。
  3. 查询元素:使用相同的两个哈希函数计算元素的两个候选桶,并检查这两个桶中是否包含该元素的指纹。
  4. 删除元素:通过查找元素的指纹并将其从桶中删除。

3.1 初始化

1.jpg

上图(a)(b)展示了一个基本的布谷鸟哈希表的插入操作,是由一个桶数组组成,每个插入项都有由散列函数h1(x)h2(x)确定的两个候选桶,具体操作上文中已经描述,此处不再赘述。

而基本的布谷鸟过滤器也是由两个或者多个哈希函数构成,布谷鸟过滤器的布谷鸟哈希表的基本单位称为条目(entry)。 每个条目存储一个指纹(fingerprint),指纹指的是使用一个哈希函数生成的n位比特位,n的具体大小由所能接受的误判率来设置,论文中的例子使用的是8bits的指纹大小。

哈希表由一个桶数组组成,其中一个桶可以有多个条目(比如上述图c中有四个条目)。而每个桶中有四个指纹位置,意味着一次哈希计算后布谷鸟有四个“巢“可用,而且四个巢是连续位置,可以更好的利用cpu高速缓存。也就是说每个桶的大小是4*8bits。

3.2 添加

布谷鸟过滤器的插入是重点,与朴素的布谷鸟哈希不同,布谷鸟过滤器采取了两个并不独立的哈希函数,具体的

\[i1 = hash(x) \]

\[i2 =i1⊕hash(f) \]

i1i2即计算出来两个桶的索引,其中第一个桶的索引是通过某个哈希函数计算出来,第二个是使用第一个索引和指纹的哈希做了一个异或操作,进行异或操作的好处是,因为异或操作的特性:同为0不同为1,且0和任何数异或是这个数的本身。那么i1也可以通过i2和指纹异或来计算。换句话说,在桶中迁走一个键,我们直接用当前桶的索引i和存储在桶中的指纹计算它的备用桶。

具体的指纹是通过哈希函数取一定量的比特位

\[f = fingerprint(x) \]

为什么不直接用索引1和指纹做异或操作,关于这个问题文中给了解释,因为指纹一般只是key映射出来的少量bit位置,那么假如不进行哈希操作,当指纹的比特位与整个桶数组相比很小时,那么备用位置使用“i⊕指纹”,将被放置到离桶i1很近的位置,比如使用八位的指纹大小,最多只能改变i1的低八位,所以也就是两个候选通的位置最多相差256,不利于均匀分配。

3.3 查找

布谷鸟过滤器的查找过程很简单,给定一个项x,算法首先根据上述插入公式,计算x的指纹和两个候选桶。然后读取这两个桶:如果两个桶中的任何现有指纹匹配,则布谷鸟过滤器返回true,否则过滤器返回false。此时,只要不发生桶溢出,就可以确保没有假阴性。

3.4 删除

标准布隆过滤器不能删除,因此删除单个项需要重建整个过滤器,而计数布隆过滤器需要更多的空间。布谷鸟过滤器就像计数布隆过滤器,可以通过从哈希表删除相应的指纹删除插入的项,其他具有类似删除过程的过滤器比布谷鸟过滤器更复杂。

具体删除的过程也很简单,检查给定项的两个候选桶;如果任何桶中的指纹匹配,则从该桶中删除匹配指纹的一份副本。

四、底层剖析

布谷鸟过滤器的核心在于布谷鸟哈希和指纹存储。以下是其底层实现的关键点:

  • 布谷鸟哈希:布谷鸟过滤器使用两个哈希函数h1h2来计算元素的候选桶。具体来说,给定一个元素x,它的两个候选桶分别为h1(x)h2(x)。如果其中一个桶有空位,则将元素的指纹存储到该桶中;如果两个桶都满了,则进行“踢出”操作,将原有元素踢出并重新插入。
  • 指纹:指纹是元素的哈希值的一部分,通常较短(例如8位)。指纹的唯一性决定了误判率。布谷鸟过滤器通过存储指纹而不是完整的元素来节省空间。
  • 误判率:布谷鸟过滤器的误判率取决于指纹的长度和桶的大小。指纹越长,误判率越低,但空间占用越大。

五、示例

以下是一个基于Spring Boot的布谷鸟过滤器实现示例:

5.1 添加依赖

pom.xml中添加Caffeine依赖:

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.1.8</version>
</dependency>

5.2 实现

import com.github.benmanes.caffeine.cache.BloomFilter;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.stereotype.Service;

@Service
public class CuckooFilterService {

    // 初始化布谷鸟过滤器,预计插入 1000 个元素,误判率为 0.01
    private BloomFilter<String> cuckooFilter = Caffeine.newBuilder()
            .maximumSize(1000)
            .buildFilter();

    /**
     * 添加元素到布谷鸟过滤器
     */
    public void add(String element) {
        cuckooFilter.put(element);
    }

    /**
     * 判断元素是否可能存在
     */
    public boolean mightContain(String element) {
        return cuckooFilter.mightContain(element);
    }

    /**
     * 删除元素(布谷鸟过滤器不支持直接删除,可以通过重建过滤器实现)
     */
    public void clear() {
        cuckooFilter = Caffeine.newBuilder()
                .maximumSize(1000)
                .buildFilter();
    }
}

5.3 测试

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/cuckoo")
public class CuckooFilterController {

    @Autowired
    private CuckooFilterService cuckooFilterService;

    @PostMapping("/add")
    public String addElement(@RequestParam String element) {
        cuckooFilterService.add(element);
        return "Added: " + element;
    }

    @GetMapping("/check")
    public String checkElement(@RequestParam String element) {
        boolean exists = cuckooFilterService.mightContain(element);
        return "Element " + element + " might exist: " + exists;
    }

    @DeleteMapping("/clear")
    public String clearFilter() {
        cuckooFilterService.clear();
        return "Filter cleared";
    }
}

5.4 运行测试

启动Spring Boot项目后,访问以下URL进行测试:

添加元素:http://localhost:8080/cuckoo/add?element=test
检查元素:http://localhost:8080/cuckoo/check?element=test
清空过滤器:http://localhost:8080/cuckoo/clear

六、拓展

6.1 布谷鸟过滤器与布隆过滤器区别

特性 布隆过滤器 布谷鸟过滤器
空间效率 更高
查询速度 O(k) O(1)
误判率 可控制 可控制
删除操作 不支持 支持
实现复杂度 简单 复杂
插入性能 稳定 高负载时可能下降
posted @ 2025-05-17 18:31  夏尔_717  阅读(399)  评论(0)    收藏  举报