布隆过滤器原理与使用场景

布隆过滤器简介

布隆过滤器(Bloom Filter)由布隆( Burton Howard Bloom )在 1970 年提出的。它由一个很长的二进制向量 (位向量) 和一系列随机均匀分布的散列 (哈希) 函数组成。用多个散列函数,将每个数据映射到位数组中,这样可以高效地插入元素或者判断某个元素可能存在一定不存在,而且可以减少内存空间开销。

假设:

  • 数据集 A = A = A={ a 1 , a 2 , ⋯   , a n a_1,a_2,\cdots,a_n a1,a2,,an},即有 n n n 个元素。
  • 布隆过滤器用一个长为 m m m 的位向量 V = V = V= { b 1 , b 2 , ⋯   , b m b_1,b_2,\cdots,b_m b1,b2,,bm} 表示集合中的元素,位向量的初始值全为 0。
  • 对应有 k k k 个 相互独立的 h a s h hash hash 函数。

流程:
在这里插入图片描述

s.t.1
对集合的每个元素,通过 k k k h a s h hash hash 函数将这个元素映射成一个位数组中的 k k k 个位置,把它们置为 1,检索时这些点如果是 1,则被检元素可能存在,如果这些点当中至少有一个为 0,则被检元素一定不存在。

s.t.2
int 数组中的每个元素都只占用 4 个字节 ,并且每个元素只能是 0 或者 1。假如申请一个长度为 10000 的 int 数组,现在有一个需求,去重 1000 万个字符串,只要让每个字符串都经过 m m m h a s h hash hash 函数将数组位置为 1,这样就不用存 1000 万个字符串的大小了,仅仅有长为 10000 的 int 数组就可以完成这个任务,这样可以大大减少内存空间。虽然可能会误判,不过大样本,小概率事件可以容忍。

使用场景

  • 去重:比如爬给定网址的时候对已经爬取过的 URL 去重。
  • 判断给定数据是否存在、 防止缓存穿透、邮箱的垃圾邮件过滤、黑名单功能等等。

缓存穿透
服务调用方每次都是查询不在缓存中的数据,这样每次服务调用都会到数据库中进行查询,如果这类请求比较多的话,就会导致数据库压力增大,这样缓存就失去了意义。

P、m、k 的确定

P P P 为人为允许的误判率, m m m 为数组的长度, k k k 为 hash 函数的个数。
m = − n l n P ( l n 2 ) 2 k = m n l n 2 \begin{aligned} m&= -\frac{nlnP}{(ln2)^2} \\\\ k&=\frac{m}{n}ln2 \end{aligned} mk=(ln2)2nlnP=nmln2
关于公式的推导可参考这篇论文 <<布隆过滤器算法误判率的分析与应用>>

优缺点

优点:

  1. 在增加元素或查询元素的时候其时间复杂度都为 O(k),且该时间复杂度与数据量的大小没有关系。
  2. hash 函数相互独立,在硬件方面实现并行运算很方便。
  3. 布隆过滤器不需要存储元素本身,所以对某些数据要求严格保密的情况下选择布隆过滤器更好。
  4. 能够承担一定的误判时,布隆过滤器和其他的数据结构对比更具有空间优势。
  5. 数据量很大时,布隆过滤器可以表示全集,而其他数据结构不能。
  6. 使用同一组散列函数的布隆过滤器可以进行交、 并、差运算。

缺点:

  1. 存在误判,它一定能判断某个元素是否不存在。
  2. 一般不能从布隆过滤器中删除元素,因为其他数据所置为 1 的位置,可能一起被删除,导致数据的缺失。

guava 实现布隆过滤器

导入 Maven 依赖:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>20.0</version>
</dependency>

测试:

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;

public class test {
    
    
    public static void main(String[] args){
    
    
        int size = 1000000;//预计要插入多少数据

        double fpp = 0.001;//期望的误判率

        BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, fpp);

        //插入数据
        for (int i = 0; i < 1000000; i++) {
    
    
            bloomFilter.put(i);
        }
        int count = 0;
        for (int i = 1000000; i < 2000000; i++) {
    
    
            if (bloomFilter.mightContain(i)) {
    
    
                count++;
                System.out.println(i + "误判了");
            }
        }
        System.out.println("总共的误判数:" + count);
        System.out.println("误判率:" + (double) count / size);
    }
}

运行结果:

1993323误判了
1994654误判了
1995990误判了
1996133误判了
1996793误判了
1997052误判了
总共的误判数:994
误判率:9.94E-4

可见误判率为 0.000994,接近我们期望的误判率 0.001

猜你喜欢

转载自blog.csdn.net/lhrfighting/article/details/119900596