布隆过滤器

假若有1亿个不重复的正整数(大体范围已知),可是只有1G的内存可用,如何判断该范围内的某个数是否出如今这1亿个数中?最经常使用的处理办法是利用位图,1*108/1024*1024*8=11.9,也只须要申请12M的内存。可是若是是1亿个邮件地址,如何肯定某个邮件地址是否在这1亿个地址中?这个时候可能你们想到的最经常使用的办法就是利用Hash表了,可是你们能够细想一下,若是利用Hash表来处理,必须开辟空间去存储这1亿个邮件地址,由于在Hash表中不可能避免的会发生碰撞,假设一个邮件地址只占8个字节,为了保证Hash表的碰撞率,因此须要控制Hash表的装填因子在0.5左右,那么至少须要2*8*108/1024*1024*1024=1.5G的内存空间,这种状况下利用Hash表是没法处理的。这个时候要用到另一种数据结构-布隆过滤器(Bloom Filter),它是由Burton Howard Bloom在1970年提出的,它结合了位图和Hash表二者的优势,位图的优势是节省空间,可是只能处理整型值一类的问题,没法处理字符串一类的问题,而Hash表却恰巧解决了位图没法解决的问题,然而Hash太浪费空间。针对这个问题,布隆提出了一种基于二进制向量和一系列随机函数的数据结构-布隆过滤器。它的空间利用率和时间效率是不少算法没法企及的,可是它也有一些缺点,就是会有必定的误判率而且不支持删除操做。
html

  下面来讨论一下布隆过滤器的原理和它的应用。 ios

一.布隆过滤器的原理 算法

  布隆过滤器须要的是一个位数组(这个和位图有点相似)和k个映射函数(和Hash表相似),在初始状态时,对于长度为m的位数组array,它的全部位都被置为0,以下图所示: 数据库

  对于有n个元素的集合S={s1,s2......sn},经过k个映射函数{f1,f2,......fk},将集合S中的每一个元素sj(1<=j<=n)映射为k个值{g1,g2......gk},而后再将位数组array中相对应的array[g1],array[g2]......array[gk]置为1: 网页爬虫

  若是要查找某个元素item是否在S中,则经过映射函数{f1,f2.....fk}获得k个值{g1,g2.....gk},而后再判断array[g1],array[g2]......array[gk]是否都为1,若全为1,则item在S中,不然item不在S中。这个就是布隆过滤器的实现原理。 数组

  固然有读者可能会问:即便array[g1],array[g2]......array[gk]都为1,能表明item必定在集合S中吗?不必定,由于有这个可能:就是集合中的若干个元素经过映射以后获得的数值恰巧包括g1,g2,.....gk,那么这种状况下可能会形成误判,可是这个几率很小,通常在万分之一如下。 数据结构

   很显然,布隆过滤器的误判率和这k个映射函数的设计有关,到目前为止,有不少人设计出了不少高效实用的hash函数,具体能够参考:《常见的Hash算法》这篇博文,里面列举了不少常见的Hash函数。而且能够证实布隆过滤器的误判率和位数组的大小以及映射函数的个数有关,相关证实可参考这篇博文:《布隆过滤器 (Bloom Filter) 详解》。假设误判率为p,位数组大小为m,集合数据个数为n,映射函数个数为k,它们之间的关系以下: 函数

  p=2-(m/n)*ln2     可得  m=(-n*lnp)/(ln2)2=-2*n*lnp=2*n*ln(1/p) google

  k=(m/n)*ln2=0.7*(m/n) url

  能够验证若p=0.1,(m/n)=9.6,即存储每一个元素须要9.6bit位,此时k=0.7*(m/n)=6.72,即存储每一个元素须要9.6个bit位,其中有6.72个bit位被置为1了,所以须要7个映射函数。从这里能够看出布隆过滤器的优越性了,好比上面例子中的,存储一个邮件地址,只须要10个bit位,而用hash表存储须要8*8=64个bit位。

  通常状况下,p和n由用户设定,而后根据p和n的值设计位数组的大小和所需的映射函数的个数,再根据实际状况来设计映射函数。

  尤为要注意的是,布隆过滤器是不容许删除元素的,由于若删除一个元素,可能会发生漏判的状况。不过有一种布隆过滤器的变体Counter Bloom Filter,能够支持删除元素,感兴趣的读者能够查阅相关文献资料。

二.布隆过滤器的应用

  布隆过滤器在不少场合能发挥很好的效果,好比:网页URL的去重,垃圾邮件的判别,集合重复元素的判别,查询加速(好比基于key-value的存储系统)等,下面举几个例子:

  1.有两个URL集合A,B,每一个集合中大约有1亿个URL,每一个URL占64字节,有1G的内存,如何找出两个集合中重复的URL。

  很显然,直接利用Hash表会超出内存限制的范围。这里给出两种思路:

  第一种:若是不容许必定的错误率的话,只有用分治的思想去解决,将A,B两个集合中的URL分别存到若干个文件中{f1,f2...fk}和{g1,g2....gk}中,而后取f1和g1的内容读入内存,将f1的内容存储到hash_map当中,而后再取g1中的url,如有相同的url,则写入到文件中,而后直到g1的内容读取完毕,再取g2...gk。而后再取f2的内容读入内存。。。依次类推,知道找出全部的重复url。

  第二种:若是容许必定错误率的话,则能够用布隆过滤器的思想。

  2.在进行网页爬虫时,其中有一个很重要的过程是重复URL的判别,若是将全部的url存入到数据库中,当数据库中URL的数量不少时,在判重时会形成效率低下,此时常见的一种作法就是利用布隆过滤器,还有一种方法是利用berkeley db来存储url,Berkeley db是一种基于key-value存储的非关系数据库引擎,可以大大提升url判重的效率。

布隆过滤器的简易版本实现:

/*布隆过滤器简易版本 2012.11.10*/ 
#include<iostream> 
#include<bitset> 
#include<string> 
#define MAX 2<<24 
using namespace std;

bitset<MAX> bloomSet; //简化了由n和p生成m的过程  
int seeds[7]={3, 7, 11, 13, 31, 37, 61}; //使用7个hash函数  
int getHashValue(string str,int n) //计算Hash值  
{ 
    int result=0; 
    int i; 
    for(i=0;i<str.size();i++)
    { 
        result=seeds[n]*result+(int)str[i]; 
        if(result > 2<<24)
            result%=2<<24;
    } 
    return result;
} 
bool isInBloomSet(string str) //判断是否在布隆过滤器中  
{ 
    int i; 
    for(i=0;i<7;i++)
    { 
    	int hash = getHashValue(str,i); 
    	if(bloomSet[hash]==0) 
    		return false;
    } 
    return true;
} 
void addToBloomSet(string str) //添加元素到布隆过滤器  
{ 
    int i; 
    for(i=0;i<7;i++)
    { 
    	int hash = getHashValue(str,i);
        bloomSet.set(hash,1);
    }
} 
void initBloomSet() //初始化布隆过滤器  
{
    addToBloomSet("http://www.baidu.com");
    addToBloomSet("http://www.cnblogs.com");
    addToBloomSet("http://www.google.com");
} 
int main(int argc, char *argv[])
{ 
    int n;
    initBloomSet(); 
    while(scanf("%d",&n) == 1)
    { 
    	string str; while(n--)
        {
            cin>>str; 
            if(isInBloomSet(str))
                 cout<<"yes"<<endl; else cout<<"no"<<endl;
        }
        
    } 
    return 0;
}
相关文章
相关标签/搜索