Bloom Filter 知识汇总

本文深入探讨了布隆过滤器的工作原理、应用场景及其误报率分析,并提供了Java实现示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


最近在推荐系统项目中遇到一个问题,就是希望能过滤用户最近看过的item, 否则给用户推荐的如果都是他们看过的,体验会大受影响;于是就去调研了下Bloom Filter过滤器的一些知识,想看看是否适用于推荐场景及如何应用,后者将会在未来的尝试中得到答案。

然而Bloom Filter过滤器本身的学习过程,就已经十分受用;最受启发的一点思想就是:

在容错率上做一个很小的妥协,即不要求100%准确(但仍然十分近似),就能换取以前在资源限制条件下不可能完成的目标最终完美解决

这一近似思想在统计中也经常用到,所以有必要对Bloom Filter过滤器做一个较全面的梳理总结,于是有了下文:)

 

Bloom Filter介绍

 

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

 

应用场景

 

1. 黑名单,垃圾邮件过滤


最典型的一个应用就是黑名单功能,对用户名称或者IP或者Email进行过滤,每次检查时用key进行hash后,如果不在黑名单内的,肯定可以通行,如果在的则不允许通过,误判情况增加一个排除名单来进行排除。

误判情况:将正常用户判定为黑名单用户


2. 爬虫重复URL检测

在爬取网站URL时,要检测这条URL是否已经访问过。

误判情况:没有访问过的误判为访问过


3. 字典纠错

检查单词拼写是否正确

误判情况:错误的单词误判为正确。


4. 磁盘文件检测

将磁盘中或者数据库中数据key存入该结构中,检测要访问的数据是否在磁盘或数据库中,然后再发起访问,避免空查询造成磁盘或数据库压力。

误判情况:不存在该数据却误判为有该数据。

 

 

上图分别对应了三种情况:

1. filter返回说没有找到key1, 那么key1真的不在storage里,这样就避免了数据库的空查询,减轻了数据库的压力;

2. filter返回说找到key2, 于是放行key2到storage中进行查询,成功查到后返回正确结果;

3. filter返回说找到key3, 于是放行key3到storage中进行查询,但由于Bloom Filter 本身有一定的False Positive Rate(误识率), 实际上在Storage运行查询后没有,返回结果为没有找到

尽管不可避免地会有一些空查询操作(比如3),但相对不做过滤来讲,Bloom Filter 已经抵挡住了99.99...%的空操作,在使用极小内存的情况下极大地减轻了数据库的压力,作用非常明显。

 

5. CDN(squid)代理缓存技术

先查找本地有无cache,如果没有则到其他兄弟 cache服务器上去查找。为了避免无谓的查询,在每个cache服务器上保存其兄弟服务器的缓存关键字,以bloomfilter方式存储,再去其他cache服务器查找之前,先检查该结构是否有url,如果有存在url,再去对应服务器查找。

误判情况: 对应服务器不存在该URL的缓存。


下面是一个综合应用场景的链接:

http://www.quora.com/What-are-the-best-applications-of-Bloom-filters

几个专业术语


这里有必要介绍一下False Positive和False Negative的概念


False Positive中文可以理解为“假阳性”,形象的一点说就是“误报”,后面将会说道Bloom Filter存在误报的情况,现实生活中也有误报,比如说去体检的时候,医生告诉你XXX检测是阳性,而实际上是阴性,也就是说误报了,是假阳性,杀毒软件误报也是同样的概念。


False Negative,中文可以理解为“假阴性”,形象的一点说是“漏报”。医生告诉你XXX检测为阴性,实际上你是阳性,你是有病的(Sorry, it’s just a joke),那就是漏报了。同样杀毒软件也存在漏报的情况。

 

Bloom Filter算法


初始状态下,Bloom Filter是一个m位的位数组,且数组被0所填充。同时,我们需要定义k个不同的hash函数,每一个hash函数都随机的将每一个输入元素映射到位数组中的一个位上。那么对于一个确定的输入,我们会得到k个索引。

插入元素:经过k个hash函数的映射,我们会得到k个索引,我们把位数组中这k个位置全部置1(不管其中的位之前是0还是1)

查询元素:输入元素经过k个hash函数的映射会得到k个索引,如果位数组中这k个索引任意一处是0,那么就说明这个元素不在集合之中;如果元素处于集合之中,那么当插入元素的时候这k个位都是1。但如果这k个索引处的位都是1,被查询的元素就一定在集合之中吗?答案是不一定,也就是说出现了False Positive的情况(但Bloom Filter不会出现False Negative的情况)

在上图中,当插入x、y、z这三个元素之后,再来查询w,会发现w不在集合之中,而如果w经过三个hash函数计算得出的结果所得索引处的位全是1,那么Bloom Filter就会告诉你,w在集合之中,实际上这里是误报,w并不在集合之中。

 

Bloom Filter算法的False Positive Rate

 
Bloom Filter的误报率到底有多大?下面在数学上进行一番推敲。假设HASH函数输出的索引值落在m位的数组上的每一位上都是等可能的。那么,对于一个给定的HASH函数,在进行某一个运算的时候,一个特定的位没有被设置为1的概率是

那么,对于所有的k个HASH函数,都没有把这个位设置为1的概率是

如果我们已经插入了n个元素,那么对于一个给定的位,这个位仍然是0的概率是

那么,如果插入n个元素之后,这个位是1的概率是

如果对一个特定的元素存在误报,那么这个元素的经过HASH函数所得到的k个索引全部都是1,概率也就是

根据常数e的定义,可以近似的表示为:

 

关于误报


有时候误报对实际操作并不会带来太大的影响,比如对于HTTP缓存服务器,如果一条URL被误以为存在与缓存服务器之中,那么当取数据的时候自然会无法取到,最终还是要从原始服务器当中获取,之后再把记录插入缓存服务器,几乎没有什么不可以接受的。
对于安全软件,有着“另可错报,不可误报”的说法,如果你把一个正常软件误判为病毒,对使用者来说不会有什么影响(如果用户相信是病毒,那么就是删除这个文件罢了,如果用户执意要执行,那么后果也只能由用户来承担);如果你把一个病毒漏判了,那么对用户造成的后果是不可设想的……更有甚者,误报在某种程度上能让部分用户觉得你很专业……

 

Bloom Filter实现代码

下面给出一个简单的Bloom Filter的Java实现代码:

 

import java.util.BitSet;

publicclass BloomFilter 
{
/* BitSet初始分配2^24个bit */ 
privatestaticfinalint DEFAULT_SIZE =1<<25; 
/* 不同哈希函数的种子,一般应取质数 */
privatestaticfinalint[] seeds =newint[] { 5, 7, 11, 13, 31, 37, 61 };
private BitSet bits =new BitSet(DEFAULT_SIZE);
/* 哈希函数对象 */ 
private SimpleHash[] func =new SimpleHash[seeds.length];

public BloomFilter() 
{
for (int i =0; i < seeds.length; i++)
{
func[i] =new SimpleHash(DEFAULT_SIZE, seeds[i]);
}
}

// 将字符串标记到bits中
publicvoid add(String value) 
{
for (SimpleHash f : func) 
{
bits.set(f.hash(value), true);
}
}

//判断字符串是否已经被bits标记
publicboolean contains(String value) 
{
if (value ==null) 
{
returnfalse;
}
boolean ret =true;
for (SimpleHash f : func) 
{
ret = ret && bits.get(f.hash(value));
}
return ret;
}

/* 哈希函数类 */
publicstaticclass SimpleHash 
{
privateint cap;
privateint seed;

public SimpleHash(int cap, int seed) 
{
this.cap = cap;
this.seed = seed;
}

//hash函数,采用简单的加权和hash
publicint hash(String value) 
{
int result =0;
int len = value.length();
for (int i =0; i < len; i++) 
{
result = seed * result + value.charAt(i);
}
return (cap -1) & result;
}
}
}

 

Bloom Filter参数选择

 

1. 哈希函数选择

哈希函数的选择对性能的影响应该是很大的,一个好的哈希函数要能近似等概率的将字符串映射到各个Bit。选择k个不同的哈希函数比较麻烦,一种简单的方法是选择一个哈希函数,然后送入k个不同的参数。

 

2. Bit数组大小选择

哈希函数个数k、位数组大小m、加入的字符串数量n的关系可以参考[5]。该文献证明了对于给定的m、n,当 k = ln(2)* m/n 时出错的概率是最小的。

同时该文献还给出特定的k,m,n的出错概率。例如:根据参考文献[5],哈希函数个数k取10,位数组大小m设为字符串个数n的20倍时,false positive发生的概率是0.0000889 ,这个概率基本能满足网络爬虫的需求了。 

 

参考:

[1] http://www.programlife.net/bloom-filter.html 综述

[2] http://blog.csdn.net/lovingprince/article/details/6632328 应用

[3] http://www.cnblogs.com/heaad/archive/2011/01/02/1924195.html    

      代码实现

[4]  Pei Cao. Bloom Filters - the math.

http://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.html 参数最优化

     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值