深入了解布隆过滤器

概念

布隆过滤器(英语:Bloom Filter)是1970年由一个叫布隆的小伙子提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。

原理

布隆过滤器的原理是,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。
Bloom Filter跟单哈希函数Bit-Map不同之处在于:Bloom Filter使用了k个哈希函数,每个字符串跟k个bit对应。从而降低了冲突的概率。

深入了解布隆过滤器_第1张图片

  • 集合里面有3个元素,要把它存到布隆过滤器里面去,应该怎么做?首先是a元素,这里我们用3次计算。b、c元素也一样。
  • 元素已经存进去之后,现在我要来判断一个元素在这个容器里面是否存在,就要使用同样的三个函数进行计算。
  • 比如d元素,我用第一个函数h1()计算,发现这个位置上是1,没问题。第二个位置也是1,第三个位置也是1。
  • 如果经过三次计算得到的下标位置值都是1,这种情况下,能不能确定d元素一定 在这个容器里面呢? 实际上是不能的。比如这张图里面,这三个位置分别是把a,b,c 存进去的时候置成1的,所以即使d元素之前没有存进去,也会得到三个1,判断返回 true。
  • 我们再来看另一个元素,e元素。我们要判断它在容器里面是否存在,一样地要用这三个函数去计算。第一个位置是1,第二个位置是1,第三个位置是0。
  • e 元素是不是一定不在这个容器里面呢? 可以确定一定不存在。如果说当时已经把e元素存到布隆过滤器里面去了,那么这三个位置肯定都是1,不可能出现0。

特点

从容器的角度来说:

  1. 如果布隆过滤器判断元素在集合中存在,不一定存在
  2. 如果布隆过滤器判断不存在,一定不存在

从元素的角度来说:

  1. 如果元素实际存在,布隆过滤器一定判断存在
  2. 如果元素实际不存在,布隆过滤器可能判断存在

缺点

bloom filter之所以能做到在时间和空间上的效率比较高,是因为牺牲了判断的准确率、删除的便利性

  • 存在误判,可能要查到的元素并没有在容器中,但是hash之后得到的k个位置上值都是1。因为哈希碰撞不可避免,所以它会存在一定的误判率。这种把本来不存在布隆过滤器中的元素误判为存在的情况,我们把
    它叫做假阳性(False Positive Probability,FPP)。
  • 删除困难。一个放入容器的元素映射到bit数组的k个位置上是1,删除的时候不能简单的直接置为0,可能会影响其他元素的判断。可以采用Counting Bloom Filter

实现

  • 在使用bloom filter时,绕不过的两点是预估数据量n以及期望的误判率fpp
  • 在实现bloom filter时,绕不过的两点就是hash函数的选取以及bit数组的大小

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

Bit数组大小选择

根据预估数据量n以及误判率fpp,bit数组大小的m的计算方式:
深入了解布隆过滤器_第2张图片
位图的容量是基于元素个数和误判率计算出来的。

long numBits = optimalNumOfBits(expectedInsertions, fpp);

哈希函数选择

由预估数据量n以及bit数组长度m,可以得到一个hash函数的个数k:
深入了解布隆过滤器_第3张图片
哈希函数的选择对性能的影响应该是很大的,一个好的哈希函数要能近似等概率的将字符串映射到各个Bit。选择k个不同的哈希函数比较麻烦,一种简单的方法是选择一个哈希函数,然后送入k个不同的参数。
哈希函数个数k、位数组大小m、加入的字符串数量n的关系可以参考Bloom Filters - the math,Bloom_filter-wikipedia
根据位数组的大小,我们进一步计算出了哈希函数的个数。

int numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits);

空间

存储 100 万个元素只占用了 0.87M 的内存,生成了 5 个哈希函数。
https://hur.st/bloomfilter/?n=1000000&p=0.03&m=&k=

代码

谷歌的 Guava 里面就提供了一个现成的布隆过滤器。


   com.google.guava
   guava
   23.0

简单使用布隆过滤器:

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

public class BloomFilterTest {

    private static int total = 100000;
    private static BloomFilter bf = BloomFilter.create(Funnels.integerFunnel(), total);
    //可以设置你允许的误差率,误差范围:0 bf = BloomFilter.create(Funnels.integerFunnel(), total, 0.01);

    public static void main(String[] args) {
        // 初始化100000条数据到过滤器中
        for (int i = 0; i < total; i++) {
            bf.put(i);
        }

        // 获取随机数匹配在过滤器中存在
        int i = (int)(1+Math.random()*(10000));
        if (bf.mightContain(i)) {
            System.out.println("BloomFilter 判定存在");
        }
    }
}

使用场景

  1. 如何在海量元素中快速判断一个元素是否存在(Redis 缓存穿透)
  2. 爬虫过滤已抓到的url
  3. 垃圾邮件过滤

你可能感兴趣的:(算法)