在程序的世界中,Bloom Filter(布隆过滤器)是程序员的一把利器,通过Bloom Filter可以快速地解决项目中一些比较棘手的问题,比如Redis中常见的缓存穿透问题等。通过本篇文章总结布隆过滤器相关知识的同时,希望让更多的小伙伴了解布隆过滤器,并能够在实际项目中使用它☺!

Bloom Filter概念

Bloom Filter(布隆过滤器)是1970年由Burton Howard Bloom提出的,是一种数据结构,比较巧妙的概率型数据结构,支持高效的插入和查询。实际上就是一个很长的二进制向量和一系列随机映射函数布隆过滤器可以用于检索一个元素是否存在于一个集合中


☆☆☆ 当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想☆☆☆


布隆过滤器的优点是空间效率和查询时间远超一般的算法,缺点是有一定的误识别率和删除困难。也就是说:

  • 当布隆过滤器判断某个值存在时,这个值可能不存在
  • 当布隆过滤器判断某个值不存在时,这个值一定不存在

Bloom Filter设计原理

Bloom Filter本质上是一个长度为m比特的位数组(bit array)与k个哈希函数(hash function)组成的数据结构。位数组元素均初始化为0,所有哈希函数都可以把输入数据尽量均匀地散列。




springboot 整合redission 布隆过滤器 布隆过滤器结合redis_数组

Bloom Filter示意图



① 当插入一个元素时,将其数据分别输入k个哈希函数中,产生k个哈希值,以哈希值作为位数组中的下标,将所有k个对应的比特置为1。② 当查询(即判断元素是否存在)一个元素时,同样将其数据输入k个哈希函数,然后检查对应的k个比特。如果有任意一个比特为0,表明元素一定不在集合中;如果所有比特均为1,表明元素大概率在集合中。

如上图所示:是m=18,k=3的Bloom Filter实例。集合中的x,y,z三个元素通过三个不同的哈希函数散列到位数组中。当查询元素w时,因为有一个比特位值为0,所以w一定不存在于该集合中。

  • 看到这里,大家是否觉得和Bit-Map有相似之处呢?

Bloom Filter和Bit-Map不同之处仅在于:Bloom Filter使用了k个哈希函数,每个元素与k个bit位相对应,从而降低了冲突的概率。

Bloom Filter使用场景

  1. 判断给定数据是否存在
  1. 判断一个数字是否存在于包含大量数字的集合中(数据集很大)
  2. 防止缓存穿透(判断请求的数据是否有效避免直接绕过缓存去请求数据库)
  3. 黑名单或垃圾消息过滤等
  1. 去重
  1. 爬去给定网址URL去重

Bloom Filter实现

布隆过滤器实现版本很多,常用的是Guava中提供的实现。

  • 当我们使用Bloom Filter时,绕不过的两点是预估数据量n以及期望的误识别率fpp
  • 当我们实现Bloom Filter时,绕不过的两点是hash函数的选取以及bit位数组的大小

对于一个确定的场景,我们预估要存的数据量为n,期望的误判率为fpp,然后需要计算我们需要的bit数组的大小m,以及hash函数的个数k,并选择hash函数!

  1. bit数组大小的选择

根据预估数据量n以及误识别率fpp,bit数组大小m的计算方式为:




springboot 整合redission 布隆过滤器 布隆过滤器结合redis_实现的redis布隆过滤器_02

bit位数组大小m计算方式



  1. 哈希函数的选择

由预估数据量n以及bit位数组大小m,可以得到hash函数的个数k为:




springboot 整合redission 布隆过滤器 布隆过滤器结合redis_布隆过滤器_03

hash函数个数k计算方式



上述只是简单的选择策略!

我们实际项目使用Guava实现版本,只需要引入Guava包即可:

com.google.guava            guava
// 测试Guava布隆过滤器实现public class TestBloomFilter {    private static int total = 1000000;    private static BloomFilter bf = BloomFilter.create(Funnels.integerFunnel(), total);    public static void main(String[] args) {        // 初始化1000000条数据到过滤器中        for (int i = 0; i < total; i++) {            bf.put(i);        }        // 匹配已在过滤器中的值,是否有匹配不上的        for (int i = 0; i < total; i++) {            if (!bf.mightContain(i)) {                System.out.println("未匹配上~~~");            }        }        // 匹配不在过滤器中的10000个值,有多少匹配出来        int count = 0;        for (int i = total; i < total + 10000; i++) {            if (bf.mightContain(i)) {                count++;            }        }        System.out.println("误伤的数量:" + count);    }}

往期精彩文章

Redis系列(三):Redis持久化机制(RDB & AOF)

Redis系列(二):跳跃表详解

Redis系列(一):与Redis的第一次相识

MySQL系列之精华篇

MySQL系列(四):MVCC多版本并发控制

MySQL系列(三):数据库锁

MySQL系列(二):索引

MySQL系列(一):事务隔离级别