布隆过滤器 Bloom Filter
什么是布隆过滤器:
布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。与HashMap相比,布隆过滤器由于不用存储key值,所以占用内存较少,对于大数据量的时候对比比较明显。
布隆过滤器的实现原理:
举例说明,假如现在有10个单词,可以创建100bit的数组,然后分别对每个单词采用5个Hash函数进行运算后得到5个Hash值,将这5个值对应的数组的位置置为1。判端下一个单词是否存在于这10个单词中的时候,只要查看这个单词hash后对应的5个位置是否都是1,如果有一个位置不是1,说明这个单词一定不存在与之前10个单词之中,如果全是1,说明这个单词可能存在那10个单词当中。
由此可知,布隆过滤器只能进行插入和是否存在操作,不能进行删除操作。布隆过滤器存在一定的误判,只能保证一定不存在,但不能保证一定存在。如何减少误判率,核心思想就是减少指针碰撞,主要通过增加hash的个数和扩大bit数组的长度来减少指针碰撞的概率。
布隆过滤器的java代码应用:
pom.xml
<dependency> <groupId>com.github.alexandrnikitin</groupId> <artifactId>bloom-filter_2.12</artifactId> <version>0.11.0</version> </dependency>
具体代码示例
@RunWith(SpringRunner.class) @SpringBootTest public class BloomFilterTest { private BloomFilter<Integer> bloomFilter; private int size = 1000000; @Before public void init(){ //不设置第三个参数时,误判率默认为0.03 //bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size); //进行误判率的设置,自动计算需要几个hash函数。bit数组的长度与size和fpp参数有关 //过滤器内部会对size进行处理,保证size为2的n次幂。 bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, 0.01); for(int i = 0; i < size; i++){ bloomFilter.put(i); } } @Test public void testBloomFilter(){ for(int i = 0; i < size; i++){ if(!bloomFilter.mightContain(i)){ //不会打印,因为不存在的情况不会出现误判 System.out.println("不存在的误判" + i); } } List<Integer> list = new ArrayList<>(1000); for (int i = size + 10000; i < size + 20000; i++) { if (bloomFilter.mightContain(i)) { list.add(i); } } //根据设置的误判率 System.out.println("存在的误判数量:" + list.size()); } }
布隆过滤器有以下应用场景:
1、黑名单,比如邮件黑名单过滤器,判端邮件地址是否在黑名单中。
2、网络爬虫,判端url是否已经被爬取过。
3、首次访问,判端访问网站的IP是否是第一次访问。
4、缓存击穿,防止非法攻击,频繁发送无法命中缓存的请求,导致缓存击穿,最总引起缓存雪崩。
5、检查英文单词是否拼写正确。
6、K-V系统快速判断某个key是否存在,典型的例子有Hbase,Hbase的每个Region中都包含一个BloomFilter,用于在查询时快速判断某个key在该region中是否存在,如果不存在,直接返回,节省掉后续的查询。
扩展,如何让布隆过滤器支持删除。
进行计数删除,但是计数删除需要存储一个数值,而不是原先的 bit 位,会增大占用的内存大小。这样的话,增加一个值就是将对应索引槽上存储的值加一,删除则是减一,判断是否存在则是看值是否大于0。