C++ 数据结构之布隆过滤器
布隆过滤器
一、历史背景知识
布隆过滤器(BloomFilter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远超过一般的算法,缺点是有一定的误识别率和删除错误。而这个缺点是不可避免的。但是绝对不会出现识别错误的情况出现(即假反例Falsenegatives,如果某个元素确实没有在该集合中,那么BloomFilter是不会报告该元素存在集合中的,所以不会漏报)
在FBI,一个嫌疑人的名字是否已经在嫌疑名单上;在网络爬虫里,一个网址是否被访问过等等。最直接的方法就是将集合中全部的元素存在计算机中,遇到一个新元素时,将它和集合中的元素直接比较即可。一般来讲,计算机中的集合是用哈希表(hashtable)来存储的。它的好处是快速准确,缺点是费存储空间。当集合比较小时,这个问题不显著,但是当集合巨大时,哈希表存储效率低的问题就显现出来了。
比如说,一个象Yahoo,Hotmail和Gmai那样的公众电子邮件(email)提供商,总是需要过滤来自发送垃圾邮件的人(spamer)的垃圾邮件。一个办法就是记录下那些发垃圾邮件的email地址。由于那些发送者不停地在注册新的地址,全世界少说也有几十亿个发垃圾邮件的地址,将他们都存起来则需要大量的网络服务器。如果用哈希表,每存储一亿个email地址,就需要1.6GB的内存(用哈希表实现的具体办法是将每一个email地址对应成一个八字节的信息指纹,然后将这些信息指纹存入哈希表,由于哈希表的存储效率一般只有50%,因此一个email地址需要占用十六个字节。一亿个地址大约要1.6GB,即十六亿字节的内存)。因此存贮几十亿个邮件地址可能需要上百GB的内存。除非是超级计算机,一般服务器是无法存储的[2]。
二、布隆过滤器原理以及优缺点
如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(哈希表,Hashtable)等数据结构都是这种思想。但是随着集合中元素的增加,我们需要的存储空间越来越大。同时检索速度也会越来越慢。
BloomFilter是一种空间效率很高的随机数据结构,BloomFilter可以看做是对bit-map的扩展,它的原理是:
当一个元素被加入集合中时,通过K个hash函数将这个元素映射成一个位阵列(Bitarray)中的K个点,将它们置成1.检索时,我们只需要看这些点是不是都是1就能(大约)知道集合中有没有它:
如果这些点中有任何一个0,则被检索元素一定不在;
如果都是1,则被检索元素很可能在。
优点:
它的优点是空间效率和查询时间都远远超过一般的算法,布隆过滤器存储空间和插入\查询时间都是O(K),另外,散列函数相互之间没有关系,方便硬件并行实现,布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。
缺点:
1、布隆过滤器的缺点和优点同样明显。误算率是其中之一。随着存入元素的增加,误算率随之增加。但是元素数量太少,则使用散列就可以了。
2、一般情况下不能从布隆过滤器中删除元素,我们很容易想到把位数组变成整数数组,每插入一个元素相应的计数器加1,这样删除元素时将计数器减掉就可以了。然而要保证安全地删除元素并非这么简单。首先我们必须保证删除的元素的确存在布隆过滤器里面,另外计数器回绕也会造成问题。
三、example
GoogleChrome浏览器使用Bloomfilter识别恶意链接(能用较小的存储空间表示较大的数据集合,简单想就是把每一个URL都可以映射成bit)的多,并且误判率在万分之一以下。
C++实现
bit_set.h
#pragmaonce #includeusingnamespacestd; #include classBitset { public: Bitset(size_tvalue) { _a.resize((value>>5)+1,0); } boolset(size_tnum) { size_tindex=num>>5; size_tpos=num%32; if(_a[index]&(1<<(31-pos))) { returnfalse; } else { _a[index]|=(1<<(31-pos)); _size++; returntrue; } } boolReset(size_tnum) { size_tindex=num>>5; size_tpos=num%32; if(Text(num)) { _a[index]&=~(1<<(31-pos)); _size--; returntrue; } else { returnfalse; } } boolText(size_tnum) { size_tindex=num>>5; size_tpos=num%32; return_a[index]&(1<<(31-pos)); } private: vector _a; size_t_size; };
Hash.h
#pragmaonce template//各类哈希字符串转换函数 size_tBKDRHash(constchar*str) { registersize_thash=0; while(size_tch=(size_t)*str++) { hash=hash*131+ch; } returnhash; } template size_tSDBMHash(constchar*str) { registersize_thash=0; while(size_tch=(size_t)*str++) { hash=65599*hash+ch; } returnhash; } template size_tRSHash(constchar*str) { size_thash=0; size_tmagic=63689; while(size_tch=(size_t)*str++) { hash=hash*magic+ch; magic*=378551; } returnhash; } template size_tAPHash(constchar*str) { registersize_thash=0; size_tch; for(longi=0;ch=(size_t)*str++;i++) { if((i&1)==0) { hash^=((hash<<7)^ch^(hash>>3)); } else { hash^=(~((hash<<11)^ch^(hash>>5))); } } returnhash; } template size_tJSHash(constchar*str) { if(!*str) { return0; } size_thash=1315423911; while(size_tch=(size_t)*str++) { hash^=((hash<<5)+ch+(hash>>2)); } returnhash; }
Bloom_Filter.h
#pragmaonce #include"bite_set.h" #include"Hash.h" #includetemplate struct__HashFunk1 { size_toperator()(constT&key) { returnBKDRHash (key.c_str()); } }; template struct__HashFunk2 { size_toperator()(constT&key) { returnSDBMHash (key.c_str()); } }; template struct__HashFunk3 { size_toperator()(constT&key) { returnRSHash (key.c_str()); } }; template struct__HashFunk4 { size_toperator()(constT&key) { returnAPHash (key.c_str()); } }; template struct__HashFunk5 { size_toperator()(constT&key) { returnJSHash (key.c_str()); } }; template , classHashFunk2=__HashFunk2 , classHashFunk3=__HashFunk3 , classHashFunk4=__HashFunk4 , classHashFunk5=__HashFunk5 > classBoolFilter { public: BoolFilter(size_tn) :_a(n*10) ,_range(n*10) {} voidset(constK&key) { _a.set(HashFunk1()(key)%_range); _a.set(HashFunk2()(key)%_range); _a.set(HashFunk3()(key)%_range); _a.set(HashFunk4()(key)%_range); _a.set(HashFunk5()(key)%_range); } boolText(constK&key) { if(!_a.Text(HashFunk1()(key)%_range)) returnfalse; if(!_a.Text(HashFunk2()(key)%_range)) returnfalse; if(!_a.Text(HashFunk3()(key)%_range)) returnfalse; if(!_a.Text(HashFunk4()(key)%_range)) returnfalse; if(!_a.Text(HashFunk5()(key)%_range)) returnfalse; returntrue; } private: Bitset_a; size_t_range; };