在程序的世界中,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,所有哈希函数都可以把输入数据尽量均匀地散列。
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使用场景
- 判断给定数据是否存在
- 判断一个数字是否存在于包含大量数字的集合中(数据集很大)
- 防止缓存穿透(判断请求的数据是否有效避免直接绕过缓存去请求数据库)
- 黑名单或垃圾消息过滤等
- 去重
- 爬去给定网址URL去重
Bloom Filter实现
布隆过滤器实现版本很多,常用的是Guava中提供的实现。
- 当我们使用Bloom Filter时,绕不过的两点是预估数据量n以及期望的误识别率fpp
- 当我们实现Bloom Filter时,绕不过的两点是hash函数的选取以及bit位数组的大小
对于一个确定的场景,我们预估要存的数据量为n,期望的误判率为fpp,然后需要计算我们需要的bit数组的大小m,以及hash函数的个数k,并选择hash函数!
- bit数组大小的选择
根据预估数据量n以及误识别率fpp,bit数组大小m的计算方式为:
bit位数组大小m计算方式
- 哈希函数的选择
由预估数据量n以及bit位数组大小m,可以得到hash函数的个数k为:
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系列(一):事务隔离级别