布隆过滤器
布隆过滤器(Bloom filter)是一个高空间利用率数据结构,由Burton Bloom于1970年提出。被用于测试一个元素是否在集合中(由于集合无重复元素的性质,可用来判重)。
布隆过滤器的优势
布隆过滤器的思想类似位图,主要是由一个很长的二进制向量和若干个(k个)散列映射函数组成。因为每个元数据的存储信息值固定,而且总的二进制向量固定。所以在内存占用和查询时间上都远远超过一般的算法。当然存在一定的不准确率(可以控制)和不容易删除样本数据。
应用场景
-
黑名单:比如邮件黑名单过滤器,判断邮件地址是否在黑名单中。
-
排序(仅限于 BitSet) 。
-
网络爬虫:判断某个URL是否已经被爬取过。
布隆过滤器和位图的区别
需要注意和位图不同,布隆过滤器通常有多个散列映射函数,因为传入的通常是字符串,对於单独的字符串例如‘abcd’和‘acbd’,单个散列映射函数计算的位相同,例如
当垃圾邮箱www.abcd.com已经标记的情况下,非垃圾邮箱www.acbd.com通过散列映射函数计算的地址和www.abcd.com相同,导致在误判认为www.acbd.com也是垃圾邮箱。
假如我们有多个地址来映射一个字符串就能大大减少这种情况。
布隆过滤器算法思想
- 需要n个散列映射函数,每个散列映射函数将传入的字符串映射为不同的key值
- 初始化,需要设置k个比特大小的数组用来映射(位图)
- 某个key插入布隆过滤器,用散列函数计算出对应的比特位,并将其置为1
- 判断某个key是否在过滤器中,需要先计算对应的比特位,判断是否都为1,如果成立,说明在里面,否则不在(存在误判情况)
布隆算法优缺点
优点:不需要储存key,节省空间
- 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势
缺点:
- 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白 名单,存储可能会误判的数据)
- 不能获取元素本身
- 一般情况下不能从布隆过滤器中删除元素
- 如果采用计数方式删除,可能会存在计数回绕问题 。
误判产生的原因
如下图假设有一个非垃圾邮箱www.dcba.com,经过计算的比特位都被标记,就会被误判为垃圾邮箱。
根据布隆过滤器的原理,一般情况下不能从布隆过滤器中删除元素。
实现简易布隆过滤器
到这里,我们来实现一个简易的布隆过滤器,不过有一个问题要解决,映射n个数字要开多大的空间?有一个公式k = (m/n)In用来计算,ln大约2.7,n是数据,m是开多少位,经过计算大约为4到5倍左右,为了简单,我们取5倍。
位图的代码在笔者上一篇博客 数据结构-------位图中说明。
以下为简易布隆过滤器的实现。
template<class K=string>
struct _Hanc1
{
size_t operator()(const K& str)
{
size_t hash = 0;
for (int i=0; i < str.size(); i++)
{
hash = str[i] * 769+ hash;
}
return hash;
}
};
template<class K = string>
struct _Hanc2
{
size_t operator()(const K& str)
{
size_t hash = 0;
for (int i=0; i < str.size(); i++)
{
hash = str[i] * 97 + hash;
}
return hash;
}
};
template<class K = string>
struct _Hanc3
{
size_t operator()(const K& str)
{
size_t hash = 0;
for (int i=0; i < str.size(); i++)
{
hash = str[i] * 389 + hash;
}
return hash;
}
};
template<class K = string
,class Hanc1 = _Hanc1<K>
,class Hanc2 = _Hanc2<K>
,class Hanc3 = _Hanc3<K>
>
class Bloomfilter
{
public:
Bloomfilter(size_t size )
:_bf(size*5)
,_size(size*5)
{}
void insert(const K& k)
{
size_t index1 = Hanc1()(k) % _size;
size_t index2 = Hanc2()(k) % _size;
size_t index3 = Hanc3()(k) % _size;
_bf.insert(index1);
_bf.insert(index2);
_bf.insert(index3);
}
bool test(const K& k)
{
size_t index1 = Hanc1()(k) % _size;
size_t index2 = Hanc2()(k) % _size;
size_t index3 = Hanc3()(k) % _size;
return _bf.findbit(index1)
&& _bf.findbit(index2)
&& _bf.findbit(index3);//如果返回真,可能是不准确的,返回假是准确的。
}
private:
BitMap _bf;
size_t _size;
};