一、什么是布隆过滤器(BoomFilter)
Bloom Filter是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合。它实际上是一个很长的二进制向量和一系列随机映射函数。
特点总结为如下:
- 空间效率高的概率型数据结构,用来检查一个元素是否在一个集合中。
- 对于一个元素检测是否存在的调用,BloomFilter会告诉调用者两个结果之一:可能存在或者一定不存在。
二、使用场景
- 解决缓存穿透
我们经常会把一部分数据放在Redis等缓存,比如产品详情。这样有查询请求进来,我们可以根据产品Id直接去缓存中取数据,而不用读取数据库,这是提升性能最简单,最普遍,也是最有效的做法。一般的查询请求流程是这样的:先查缓存,有缓存的话直接返回,如果缓存中没有,再去数据库查询,然后再把数据库取出来的数据放入缓存,一切看起来很美好。但是如果现在有大量请求进来,而且都在请求一个不存在的产品Id,会发生什么?既然产品Id都不存在,那么肯定没有缓存,没有缓存,那么大量的请求都怼到数据库,数据库的压力一下子就上来了,还有可能把数据库打死。
虽然有很多办法都可以解决这问题,但是我们的主角是“布隆过滤器”,没错,“布隆过滤器”就可以解决(缓解)缓存穿透问题。至于为什么说是“缓解”,看下去你就明白了。
- 大量数据,判断给定的是否在其中
现在有大量的数据,而这些数据的大小已经远远超出了服务器的内存,现在再给你一个数据,如何判断给你的数据在不在其中。如果服务器的内存足够大,那么用HashMap是一个不错的解决方案,理论上的时间复杂度可以达到O(1),但是现在数据的大小已经远远超出了服务器的内存,所以无法使用HashMap,这个时候就可以使用“布隆过滤器”来解决这个问题。但是还是同样的,会有一定的“误判率”。
- 进行垃圾邮件过滤(垃圾数据过滤)
- 网页爬虫对 URL 的去重,避免爬取相同的 URL 地址
三、实现原理
布隆过滤器的核心实现是一个超大的位数组和几个哈希函数。假设位数组的长度为 m,哈希函数的个数为 k。
以上图为例,具体的操做流程:假设集合里面有 3 个元素 {x, y, z},哈希函数的个数为 3。首先将位数组进行初始化,将里面每一个位都设置位 0。对于集合里面的每个元素,将元素依次经过 3 个哈希函数进行映射,每次映射都会产生一个哈希值,这个值对应位数组上面的一个点,而后将位数组对应的位置标记为 1。查询 W 元素是否存在集合中的时候,一样的方法将 W 经过哈希映射到位数组上的 3 个点。若是 3 个点的其中有一个点不为 1,则能够判断该元素必定不存在集合中。反之,若是 3 个点都为 1,则该元素可能存在集合中。注意:此处不能判断该元素是否必定存在集合中,可能存在必定的误判率。能够从图中能够看到:假设某个元素经过映射对应下标为 四、五、6 这 3 个点。虽然这 3 个点都为 1,可是很明显这 3 个点是不一样元素通过哈希获得的位置,所以这种状况说明元素虽然不在集合中,也可能对应的都是 1,这是误判率存在的缘由。
优势
相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优点。布隆过滤器存储空间和插入/查询时间都是常数。另外,Hash 函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不须要存储元素自己,在某些对保密要求很是严格的场合有优点。
布隆过滤器能够表示全集,其它任何数据结构都不能。
缺点
可是布隆过滤器的缺点和优势同样明显。误算率(False Positive)是其中之一。随着存入的元素数量增长,误算率随之增长(误判补救方法是:再创建一个小的白名单,存储那些可能被误判的信息)。可是若是元素数量太少,则使用散列表足矣。
另外,通常状况下不能从布隆过滤器中删除元素。咱们很容易想到把位列阵变成整数数组,每插入一个元素相应的计数器加 1, 这样删除元素时将计数器减掉就能够了。然而要保证安全的删除元素并不是如此简单。首先咱们必须保证删除的元素的确在布隆过滤器里面. 这一点单凭这个过滤器是没法保证的。另外计数器回绕也会形成问题。
四、推荐使用Guava的布隆过滤器
Maven:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>22</version>
</dependency>
建立 BloomFilter 对象,须要传入 Funnel 对象,预估的元素个数,误判率。
BloomFilter<String> filter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), 1000,0.01);
使用 put 方法添加元素:
filter.put("test");
使用 mightContain 方法判断元素是否存在:
filter.mightContain("test");
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。