Bloom Filter详解
一、备用知识
1.1、哈希函数
- 维基百科这样说:哈希函数(散列函数)能够将任意长度的输入值转变成固定长度的值输出,该值称为散列值,输出值通常为字母与数字组合。
- 通俗来讲就是:将任意大小的输入数据转换成特定大小的输出数据的函数,转换后的数据称为哈希值或哈希编码,也叫散列值。
所有散列函数都有如下基本特性:
- 如果两个散列值是不相同的(根据同一函数),那么这两个散列值的原始输入也是不相同的。这个特性是散列函数具有确定性的结果,具有这种性质的散列函数称为单向散列函数。
- 散列函数的输入和输出不是唯一对应关系的,如果两个散列值相同,两个输入值很可能是相同的,但也可能不同,这种情况称为“散列碰撞(collision)”。
但是用 hash表存储大数据量时,空间效率还是很低,当只有一个 hash 函数时,还很容易发生哈希碰撞。
二、什么是 BloomFilter
2.1、概念
布隆过滤器(Bloom Filter)是 1970 年由布隆提出的。它实际上是一个很长的二进制向量[数组]和一系列随机映射函数。主要用于判断一个元素是否在一个集合中。
2.2、用途
一句话总结: 判断一个元素是否存在一个集合中
三、BloomFilter原理
3.1、BloomFilter数据结构
BloomFilter 是由一个固定大小的二进制向量或者位图(bitmap)和一系列映射函数组成的。
在初始状态时,对于长度为 m 的位数组,它的所有位都被置为0,如图:
当有变量被加入集合时,通过 K 个映射函数将这个变量映射成位图中的 K 个点,把它们置为 1(假定有两个变量都通过 3 个映射函数)。
查询某个变量的时候我们只要看看这些点是不是全部都是 1 就可以大概率知道集合中是否存在该值了
- 如果这些点有任何一个 0,则被查询变量一定不在;
- 如果都是 1,则被查询变量很可能存在
为什么说是可能存在,而不是一定存在呢?那是因为映射函数本身就是散列函数,散列函数是会有碰撞的。
3.2、误判率
布隆过滤器的误判是指多个输入经过hash之后在相同的bit位置1了,这样就无法判断究竟是哪个输入产生的,因此误判的根源在于相同的 bit 位被多次映射且置 1。
这种情况也造成了布隆过滤器的删除问题,因为布隆过滤器的每一个 bit 并不是独占的,很有可能多个元素共享了某一位。如果我们直接删除这一位的话,会影响其他的元素。(比如上图中的下标第 3 位)。
总的来说:
错误率越低,位数组越长,空间占用越大。
错误率越低,无偏hash函数越多,计算耗时越长。
3.3、添加与查询元素步骤
添加元素
1.通过k个无偏hash函数计算得到k个hash值
2.依次取模数组长度,得到数组索引
3.将计算得到的数组索引下标位置数据修改为1
查询元素
- 将要查询的元素给k个哈希函数
- 得到对应于位数组上的k个位置
- 如果k个位置有一个为 0,则肯定不在集合中
- 如果k个位置全部为 1,则可能在集合中
删除元素
布隆过滤器对元素的删除不太支持,目前有一些变形的特定布隆过滤器支持元素的删除!关于为什么对删除不太支持,其实也非常好理解,hash冲突必然存在,删除肯定是很苦难的!
四、Bloom Filter 总结
4.1、Bloom Filter 优点
相比于其它的数据结构,布隆过滤器存储空间和插入/查询时间都是常数 O(K),另外,散列函数相互之间没有关系。布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。
布隆过滤器可以表示全集,其它任何数据结构都不能。
4.2、Bloom Filter 缺点
布隆过滤器的缺点和优点一样明显。误算率是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。
- 有一定的误判率,但是可以通过调整参数来降低
- 无法获取元素本身
- 很难删除元素
4.3、什么情况下需要布隆过滤器?
比较常见的例子
- redis缓存穿透问题【重点】
- 字处理软件中,需要检查一个英语单词是否拼写正确
- 在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上
- 在网络爬虫里,一个网址是否被访问过
- yahoo, gmail等邮箱垃圾邮件过滤功能