1. 什么是布隆过滤器(Bloom Filter)
布隆过滤器(Bloom Filter)是1970年由布隆提出的。
它实际上是一个很长的二进制位数组和一系列随机映射函数。
布隆过滤器可以用于检索一个元素是否在一个集合中。
它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
2. 布隆过滤器的原理
2.1. 数据结构
- 以Redis中的布隆过滤器实现为例,Redis中的布隆过滤器底层是一个大型位数组(二进制数组)+ 多个无偏hash函数。
- 一个大型位数组(二进制数组):
- 多个无偏hash函数:无偏hash函数就是能把元素的hash值计算得比较均匀的hash函数,能使得计算后的元素下标比较均匀的映射到位数组中。如下就是一个简单的布隆过滤器示意图,其中k1、k2代表增加的元素,a、b、c即为无偏hash函数,最下层则为二进制数组。
2.2. 空间计算
- 在布隆过滤器增加元素之前,首先需要初始化布隆过滤器的空间,也就是上面说的二进制数组,除此之外还需要计算无偏hash函数的个数。
- 布隆过滤器提供了两个参数,分别是预计加入元素的大小n,运行的错误率f。
- 布隆过滤器中有算法根据这两个参数会计算出二进制数组的大小l,以及无偏hash函数的个数k。它们之间的关系比较简单:
- 错误率越低,位数组越长,控件占用较大;
- 错误率越低,无偏hash函数越多,计算耗时较长;
- 如下地址是一个免费的在线布隆过滤器在线计算的网址:
2.3. 增加元素
1.往布隆过滤器增加元素,添加的key需要根据k个无偏hash函数计算得到多个hash值,然后对数组长度进行取模得到数组下标的位置,然后将对应数组下标的位置的值置为1;
1.通过k个无偏hash函数计算得到k个hash值;
2.依次取模数组长度,得到数组索引;
3.将计算得到的数组索引下标位置数据修改为1
例如,key = Liziba,无偏hash函数的个数k=3,分别为hash1、hash2、hash3。三个hash函数计算后得到三个数组下标值,并将其值修改为1;如图所示:
2.4. 查询元素
布隆过滤器最大的用处就在于判断某样东西一定不存在或者可能存在,而这个就是查询元素的结果。其查询元素的过程如下:
- 通过k个无偏hash函数计算得到k个hash值;
- 依次取模数组长度,得到数组索引;
- 判断索引处的值是否全部为1,如果全部为1则存在(这种存在可能是误判),如果存在一个0则必定不存在;
2.关于误判,其实非常好理解,hash函数在怎么好,也无法完全避免hash冲突,也就是说可能会存在多个元素计算的hash值是相同的,那么它们取模数组长度后的到的数组索引也是相同的,这就是误判的原因。
2.5. 修改元素
不支持;
2.6. 删除元素
- 布隆过滤器对元素的删除不太支持,但是目前有一些变形的特定布隆过滤器支持元素的删除;
- 关于为什么对删除不太支持,其实也非常好理解,hash冲突必然存在,删除肯定是很困难的!
3. 假阳性率及最佳实践
- 所谓假阳性率就是本来在集合中不存在的元素,被判定为存在的概率。
- 假阳性是BF最大的痛点,因此有必要权衡,比如计算一下假阳性的概率。为了简单一点,就假设我们的哈希函数选择位数组中的比特时,都是等概率的。当然在设计哈希函数时,也应该尽量满足均匀分布。
- 在位数组长度m的BF中插入一个元素,它的其中一个哈希函数会将某个特定的比特置为1。因此,在插入元素后,该比特仍然为0的概率是:
- 现有k个哈希函数,并插入n个元素,自然就可以得到该比特仍然为0的概率是:
- 反过来讲,它已经被置为1的概率就是:
- 也就是说,如果在插入n个元素后,我们用一个不在集合中的元素来检测,那么被误报为存在于集合中的概率(也就是所有哈希函数对应的比特都为1的概率)为:
- 当n比较大时,根据重要极限公式,可以近似得出假阳性率:
因此,可以得出如下结论,在哈希函数的个数k一定的情况下:
- 位数组长度m越大,假阳性率越低;
- 已插入元素的个数n越大,假阳性率越高;
- 如何选择适合业务的 k 和 m 值呢,直接贴一个公式:
4. 布隆过滤器的优点
- 相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数(即hash函数的个数),增加和查询元素的时间复杂为O(N);
- 布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势;
- 存储空间小,如果允许存在一定的误判,布隆过滤器是非常节省空间的(相比其他数据结构如Set集合);
- 布隆过滤器可以表示全集,其它任何数据结构都不能。
5. 布隆过滤器的缺点
- 有点一定的误判率,但是可以通过调整参数来降低;
- 无法获取元素本身;
- 很难删除元素;
6. 布隆过滤器的使用场景
利用布隆过滤器减少磁盘 IO 或者网络请求,因为一旦一个值必定不存在的话,就可以直接结束查询,比如以下场景:
- 大数据去重;
- 网页爬虫对 URL 的去重,避免爬取相同的 URL 地址;
- 解决Redis缓存穿透问题,将已存在的缓存放到布隆过滤器中,访问不存在的缓存时迅速返回避免缓存及数据库挂掉;
- 邮件过滤,使用布隆过滤器来做邮件黑名单过滤;
- 推荐算法,推荐过的不再推荐(类似抖音刷过的往下滑动不再刷到);
- HBase、RocksDB、LevelDB等数据库内置布隆过滤器,用于判断数据是否存在,可以减少数据库的IO请求;