C++布隆过滤器BloomFilter的简单实现
1.引言
在谈布隆过滤器之前,我们首先需要知道哈希和位图。哈希,就是映射,位图,就是一个个bit位,一般用来判断在不在的模型。但是位图有一个很致命的缺点是:无法映射字符串等类型,所以引入了布隆过滤器。而布隆过滤器,不仅解决了映射字符串的问题,而且还在哈希上升级了映射规则,不再采用单一映射,而采用多映射原则即判断一个元素在不在是采用多个bit位来控制的。
那么,具体布隆过滤器是怎么样存放字符串的呢? 现在一种常见的思路是将一个字符串的每个字符的ASCII码加起来代表一个字符串即可,但是相信很多朋友在读到这里的时候肯定心里已经列举了很多反例了。是的,这种哈希规则无法避免具有相同组合阵容的ASCII,于是聪明的大佬想到了对每个字符进行乘以一个31或者131等等之类的数再加起来,这样一来,哪怕具有相同阵容的字符串也会有不一样的ASCII总值了。这就是众多哈希映射规则函数中的BKDR算法了,类似的算法还有很多,它们之所以这么做的原因可能也是参照了一些数学上的研究。我们在此次实现的映射规则上也会至少采用三种哈希算法规则来尽量避免映射冲突。
是的,只能说避免冲突,任何东西都无法保证全部,布隆过滤器也是一样,这也是它最大的缺点之一,就是存在误判。不过布隆过滤器之所以叫过滤器,就是可以当作“过滤器”配合着跟其他场景使用从而达到提高效率的目的。例如:在注册QQ判断QQ昵称的时候,倘若该昵称不存在,那么则一定是不存在的,没有其他情况。但是倘若判断存在,则还存在一种误判的场景,即别人占用了你的哈希位,这种可能存在误判情况下也需要结合着数据库来综合判定,从整体而言,效率提升的非常高。
2.实现内容
讲完了原理,那么我们来实现一个最简单的布隆过滤器。
要写之前肯定要准备好我们控制映射规则的哈希仿函数。我们之前提到了BKDR哈希算法就是其中一个,这次我们实现准备了三个,对应着一个元素映射三个bit位。它们分别是:BKDRHash、APHash和DJBHash
2.1哈希仿函数
各个哈希仿函数思路是:
BKDR:每个字符乘以31、131、13131等数字,再相加就不会重复。
AP:将整个字符串视为整体通过一定规则转化为一个中间哈希值,再由中间哈希值来生成最终哈希值就能避免重复。
DJB:它通过对字符串中的每个字符进行一系列的位运算和加法操作来生成哈希值。
struct BKDRHash //思路是:每个字符乘以31、131、13131等数字,再相加就不会重复
{
size_t operator()(const string& s)
{
size_t hash = 0;
for (auto ch : s)
{
hash += ch;
hash *= 31;
}
return hash;
}
};
struct APHash//思路:将整个字符串视为整体通过一定规则转化为一个中间哈希值,再由中间哈希值来生成最终哈希值就能避免重复
{
size_t operator()(const string& s)
{
size_t hash = 0;
for (long i = 0; i < s.size(); i++)
{
size_t ch = s[i];
if ((i & 1) == 0)
{
hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
}
else
{
hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
}
}
return hash;
}
};
struct DJBHash//思路:它通过对字符串中的每个字符进行一系列的位运算和加法操作来生成哈希值
{
size_t operator()(const string& s)
{
size_t hash = 5381;
for (auto ch : s)
{
hash += (hash << 5) + ch;
}
return hash;
}
};
2.2类结构
N代表最多插入key数据的个数 ,K代表数据类型,后面的三个模板参数代表映射规则
实现功能有:set存入函数和test检测是否在的函数
//N代表最多插入key数据的个数 ,K代表数据类型,后面的三个模板参数代表映射规则
template<size_t N,class K = string,
class Hash1 = BKDRHash,
class Hash2 = APHash,
class Hash3 = DJBHash>
class BloomFilter
{
public:
void set(const K& key) //设置key值存入
{}
bool test(const K& key)//判断key值在不在
{}
private:
bitset<N> _bs; //底层是位图
};
2.3set()存入函数
分别设置三个不同哈希函数所对应的哈希值来存入。
void set(const K& key)
{
size_t hash1 = Hash1()(key) % N;
_bs.set(hash1);
size_t hash2 = Hash2()(key) % N;
_bs.set(hash2);
size_t hash3 = Hash3()(key) % N;
_bs.set(hash3);
cout << hash1 << " " << hash2 << " " << hash3 << endl;
}
2.4test()判断函数
思路是只要有一个位置不在位图中即不在布隆过滤器中。
而且一个值在是不准确的,存在误判 因为可能本来不在的值会被别人已经映射了,导致误判存在了,但是一个值不在一定是准确的
bool test(const K& key)
{
size_t hash1 = Hash1()(key) % N;
if (!_bs.test(hash1)) //但凡有一个位置不在就不在了
{
return false;
}
size_t hash2 = Hash2()(key) % N;
if (!_bs.test(hash2))
{
return false;
}
size_t hash3 = Hash3()(key) % N;
if (!_bs.test(hash3))
{
return false;
}
//一个值在是不准确的,存在误判 因为可能本来不在的值会被别人已经映射了,导致误判存在了
//一个值不在一定是准确的
return true;
}
2.5测试
使用以下测试
void test_bloomFilter()
{
BloomFilter<100> bs;
//放入以下四个
bs.set("sort");
bs.set("srot");
bs.set("bloom");
bs.set("hello world hello Arthur");
bs.set("world hello Arthur hello ");
cout<<bs.test("tros")<<endl; //不在
cout<<bs.test("srot")<<endl; //在
}
可以看到,测试已经成功了。