认识布隆过滤器(Bloom Filter)

摘录自《程序员代码面试指南》

如果遇到网页黑名单系统、垃圾邮件过滤系统、爬虫的网址判重等题目,又看到系统容忍一定程度的失误率,但是对空间要求比较严格,那么很有可能是需要关于布隆过滤器的知识。一个布隆过滤器精确地代表一个集合,并可以精确判断一个元素是否在集合中。注意,只是精确代表和精确判断,到底有多精确呢?则完全在于你具体的设计,但想做到完全正确是不可能的。布隆过滤器的优势就在于使用很少的空间就可以将准确率做到很高的程度,该结构由 Burton Howard Bloom 于 1970 年提出。

首先介绍哈希函数(散列函数)的概念。哈希函数的输入域可以是非常大的范围,比如,任意一个字符串,但是输入域是固定的范围,假设为 S,并具有如下性质:

  • 典型的哈希函数都有无限的输入值域;
  • 当给哈希函数传入相同的输入值时,返回值一样;
  • 当给哈希函数传入不同的输入值时,返回值可能一样,也可能不一样,这是当然的,因为输出域统一是 S,所以会有不同的输入值对应在 S 中的一个元素上;
  • 最重要的是性质是很多不同的输入值所得到的返回值会均匀分布在 S 上;

第 1~3 点性质是哈希函数的基础,第 4 性质是评价一个哈希函数优劣的关键,不同输入值所得到所有返回值越均匀地分布到 S 上,哈希函数越优秀,并且这种均匀分布与输入值出现的规律无关。比如,“aaa1”、“aaa2”、“aaa3”三个输入值比较类似,但经过优秀的哈希函数计算后的结果应该相差非常大。一些哈希函数经典的实现,比如 MD5 和 SHA1 算法。如果一个优秀的哈希函数能够做到很多不同的输入值所得到的返回值非常均匀地分布在 S 上,那么将所有的返回值对 m 取余(%m),可以认为所有的返回值也会均匀地分布在 0~m-1的空间上。

接下来介绍什么是布隆过滤器。假设有一个长度为 m 的 bit 类型的数组,即数组中的每一个位置只占一个 bit,每一个 bit 只有 0 和 1 两种状态,如下图所示:
在这里插入图片描述
再假设一共有 k 个哈希函数,这些函数的输出域 S 都大于或等于 m,并且这些哈希函数都足够优秀,彼此之间也完全独立。那么对同一个输入对象(假设是一个字符串记为 URL),经过 k 个哈希函数算出来的结果也是独立的,可能相同,也可能不同,但彼此独立。对算出来的每一个结果都对 m 取余(%m),然后在 bit array 上把对应的位置设置为 1(涂黑),如下图所示:
在这里插入图片描述
把 bit 类型的数组记为 bitMap。至此,一个输入对象对 bitMap 的影响过程就结束了,也就是 bitMap 中的一些位置被涂黑。接下来按照该方法处理所有涂黑的位置,遇到已经涂黑的位置让其继续为黑即可。处理完所有的输入对象后,可能 bitMap 中已经有相当多的位置被涂黑。至此,一个布隆过滤器生成完毕,这个布隆过滤器代表之前所有输入对象组成的集合。

那么在检查阶段时,如何检查某一个对象是否是之前的某一个输入对象呢?假设一个对象为 a,想检查它是否是之前的输入对象,就把 a 通过 k 个哈希函数算出 k 个值,然后把 k 个值取余(%m),就得到 [0, m-1] 范围上的 k 个值。接下来在 bitMap 上看这些位置是不是都为黑。如果有一个不为黑,说明 a 一定不在这个集合里。如果都为黑,说明 a 在这个集合里,但可能有误判。再解释具体一点,如果 a 的确是输入对象,那么在生成布隆过滤器时,bitMap 中相应的 k 个位置一定已经涂黑了,所以在检查阶段,a 一定不会被漏过,这个不会产生误判。会产生误判的是,a 明明不是输入对象,但如果在生成布隆过滤器的阶段因为输入对象过多,而 bitMap 过小,则会导致bitMap 绝大多数的位置都已经变黑。那么在检查 a 时,可能 a 对应的 k 个位置都是黑的,从而错误地认为 a 是输入对象。通俗地说,布隆过滤器的失误类型是“宁可错杀三千,绝不放过一个”。

关于布隆过滤器的实现请参考《程序代码面试指南》——左程云著,或者 https://www.cnblogs.com/allensun/archive/2011/02/16/1956532.html