最近身边的朋友讨论「布隆过滤器」的频次有点多,刚好自己也不太了解,于是乎研究一下。学习一个知识,首先了解该知识能带来的价值是什么,那么撇开算法本身,先来了解下这个东西的实际应用场景,大概的场景有以下几种:
- 解决缓存穿透问题
- 网络爬虫对URL去重
- 垃圾邮件过滤
- 秒杀系统
什么是布隆过滤器
本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构,它的特点是高效地插入和查询,而根据查询结果可以判断某个对象 一定不存在或者可能存在。
相比于传统的List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是得到结果是概率性的,而不是确切的,同时布隆过滤器还有一个缺陷就是 数据只能插入不能删除。
数据如何存入布隆过滤器
布隆过滤器是由一个很长的bit数组
和一系列哈希函数
组成的。数组的每个元素都只占1bit空间,并且每个元素只能为0或1。布隆过滤器还拥有k个哈希函数,当一个元素加入布隆过滤器时,会使用k个哈希函数对其进行k次计算,得到k个哈希值,并且根据得到的哈希值,在bit数组中把对应下标的值置位1。
判断某个数是否在布隆过滤器中,就对该元素进行k次哈希计算,得到的值在bit数组中判断对应位置的所有元素是否都为1,如素都为1,就说明这个值在布隆过滤器中。
为什么会有误判
当插入的元素越来越多时,当一个不在布隆过滤器中的元素,经过同样规则的哈希计算之后,得到的值在bit数组中查询,有可能这些位置因为其他的元素提前被置1了。因此布隆过滤器存在误判的情况,但是如果布隆过滤器判断某个元素不在布隆过滤器中,那么这个值就一定不在。
应用场景举例
背景
现在有个100亿个黑名单网页数据,每个网页的URL占用64字节。现在想要实现一种网页过滤系统,可以根据网页的URL判断该网站是否在黑名单上,请设计该系统。
需求
可以允许有0.01%以下的判断失误率,并且使用的总空间不要超过200G。
分析一下,这里一共有4个常量:
100亿条黑名单数据
,每条数据占64个字节
,万分之一的失误率
,总空间不要超过200G
。
如果不考虑布隆过滤器,那么这里存储100亿条数据就需要 $ 100亿 \times 64字节 = 596G $ 显然超过300G。
如果使用布隆过滤器,在满足有100亿条数据 并且允许万分之一的失误率的布隆过滤器需要多大的bit数组呢?
下面来分析下使用布隆过滤器的情况(设bit数组的大小为m,样本数量为n,失误率为p):
由题可知 $ n=100亿,n=0.01% $,根据布隆过滤器大小m的公式:
求得 m = 19.19n,向上取整为 20n,2000亿字节,约为186G,满足条件。
算完m,我们顺便来算下m,n已知,这时满足最小误差的k是几个。根据哈希函数个数k的公式:
求得 k = 14,即需要14个哈希函数。
通过通过 m = 20n, k = 14我们再来算下真实的失误率。根据布隆过滤器真实失误率p公式:
求得 p = 0.006%,即布隆过滤器的真实失误率为0.006%。
通过布隆过滤器公式也可以看出:
单个数据的大小不影响布隆过滤器大小,因为样本会通过哈希函数得到输出值
就好比上面的每个网页的URL占用64字节
这个数据大小跟布隆过滤器大小没啥关系。
这三个公式就是有关布隆过滤器已经推倒出的公式,下面我们来看看这个公式是如何推导出来的。
布隆过滤器公式推导
讲公式,应该先知道几个关键的常量:误判率p
、布隆过滤器长度m
、元素个数n
、哈希函数个数k
前提条件
:假设每个元素哈希得到的值分布到m数组上的每一个数组节点的概率是相等的。
假设布隆过滤器长度为m,元素个数n为1,哈希函数个数k也为1。那么在插入时某一数组节点没有被置为1的概率。
如果上面其它不变,而哈希函数个数变成k个,那么在插入时某一数组节点没有被置为1的概率。
如果元素个数变成n个,而哈希函数个数变成k个,那么在插入时某一数组节点没有被置为1的概率。
从上面推导出的是: 当布隆过滤器长度为m,元素个数变成n个,哈希函数个数变成k个的时候,某一节点被置为1的概率为
这个还需要考虑到,一个元素通过hash会生成多个k,放入m数组中,所以需要这k个值都为1才会认为该该元素已经存在,转换如下。
=>
思考
为什么上面这个公式的值就是最终的误差率?
因为当一个布隆过滤器中不存在的元素进来的是的时候,首先通过hash算法产生k个哈希值,分布在m数组上都为1的的概率不就是上面推导出的这个公式吗,那不就是误差吗?明明是不存在的值,却有这个概率表明已经存在。
思考
给定的m和n,思考k值为多少误差会最小。
为什么k值的大小不合理会影响误差呢?
我们来思考下,一个元素最终生成k个hash值,那么会在数组m上的k个位置标记为1。
假设k为1,那么每次进来只在m上的某一个位置标记为1,这样的话如果一个新元素进来刚好hash值也在这里,而不用其它位置来判断是否为1,这个误差就会比较大。
假设k为m,那么第一个元素进来,在m上所有位置上都表为1了 ,以后只要进来一个元素就会标记为已存在。这个误差也太大了。
上面只是举了两个极端的例子,但也说明k值太大、太小都不好,它的最优值一定跟m、n存在某种关系。
它们之间的关系只要记住下面这个公式就可以了。