(一)布隆过滤器的简单介绍
布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存
在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。
布隆过滤器可以是位图+哈希函数,就是通过多个哈希函数将该类型的元素转化为多个整型映射到不同的位置去,映射后就会产生许多问题,随着数据映射的次数越来越多,就会发生冲突,就是误判。
如图所示:“百度”,“字节”,“腾讯”三个字符串在映射时候“字节”和“腾讯”在其中的一个位置上发生了冲突 “腾讯”先映射成1,“字节”后映射然后覆盖了腾讯的位置,因此在测试“腾讯”的3个映射位置时候全部会显示成1,但其实其中一个1并不是“腾讯”的,而是“字节”覆盖的,这里就是误判了。
(二)布隆过滤器的一些海量数据处理题
这里有关于布隆过滤器的两道题。
1.给两个文件,分别有100亿query,我们只有1G内存,如何找到两文件交集?分别给出精确算法和近似算法。
这里假设每一个query(字符串)是50byte,一共500G。
因为只有1G内存,所以我们需要把100query切分为至少500份朝上,因为内存只有1G,500份每份都是1G,但非常不保险,容易程序容易出错,如果切分的某段区间中相同元素的或映射到同一个文件的特别多,超过一G,这时候放进内存就会爆。所以切1000份比较合适,但还是建议再往大切切,这里选择切1000份。经过HashFunc函数的计算将A和B中相同的下标的query放入同一个小文件。之后进入内存,将A的文件放入set<string> seta,同理,B的文件放入set<string> setb中。利用set容器的特性找交集即可。
这里可能会出现极端情况,就是进入同一个文件的query超过1G,就是冲突很多,这里解决办法是换一个哈希函数,继续切分,直到能进入内存。超过1G可能是重复太多也可能是映射到同一个位置的太多,如果重复太多,set自带去重属性不要担心,如果映射到同一位置的太多,就需要哈希切分。
2.如何扩展BloomFilter使它支持删除元素的操作?
每个位置改成多个位置的引用计数就可以支持,一个位置给8个Bit位标记,但这样空间消耗变大了。
3.给1个超过100G大小的 log file,log存在IP地址,设计算法找到出现次数最多的IP地址。
这题和第一题有着相似之处,这里没规定内存大小,所以只有切分合理即可,这里我选择切100份,通过HashFunc(IP)%100选择放进哪一个小文件中,这里极端情况和第一题一样,如果出现冲突,进行二次切分。接着放进内存,通过map<string,int>来统计每一个iP出现的次数,接着通过优先队列来找出出现最多的IP这里我们在优先队列需要改写仿函数,因为这里是字符串,需要改变它的比较方式。
(三)布隆过滤器的简单模拟实现及测试
#include"Bitset.h"
#include<string>
using namespace std;
struct HashFuncBKDR
{
// BKDR
size_t operator()(const string& s)
{
size_t hash = 0;
for (auto ch : s)
{
hash *= 131;
hash += ch;
}
return hash;
}
};
struct HashFuncAP
{
// AP
size_t operator()(const string& s)
{
size_t hash = 0;
for (size_t i = 0; i < s.size(); i++)
{
if ((i & 1) == 0) // 偶数位字符
{
hash ^= ((hash << 7) ^ (s[i]) ^ (hash >> 3));
}
else // 奇数位字符
{
hash ^= (~((hash << 11) ^ (s[i]) ^ (hash >> 5)));
}
}
return hash;
}
};
struct HashFuncDJB
{
// DJB
size_t operator()(const string& s)
{
size_t hash = 5381;
for (auto ch : s)
{
hash = hash * 33 ^ ch;
}
return hash;
}
};
namespace bloom
{
template<size_t N, class K = string, class Hash1 = HashFuncBKDR, class Hash2 = HashFuncAP, class Hash3 = HashFuncDJB>
class BloomFilter
{
public:
void Set(const K& key)
{
size_t hash1 = Hash1()(key) % M;
size_t hash2 = Hash2()(key) % M;
size_t hash3 = Hash3()(key) % M;
_bs.set(hash1);
_bs.set(hash2);
_bs.set(hash3);
}
bool Test(const K& key)
{
size_t hash1 = Hash1()(key) % M;
if (_bs.test(hash1) == false)
{
return false;
}
size_t hash2 = Hash2()(key) % M;
if (_bs.test(hash2) == false)
{
//cout <<key <<"位置二错误" << " ";
return false;
}
size_t hash3 = Hash3()(key) % M;
if (_bs.test(hash1) == false)
{
//cout << key<<"位置三错误" << " ";
return false;
}
return true;//存在误判,可能三个位是与别的位置冲突的,所以误判。
}
private:
static const size_t M = 10 * N;
bit::Bitset<M> _bs;
};
void TestBloomFilter1()
{
string strs[] = { "百度","字节","腾讯" };
//BloomFilter<1000, string, HashFuncBKDR, HashFuncAP, HashFuncDJB> bf;
BloomFilter<1000> bf;
for (auto& s : strs)
{
bf.Set(s);
}
for (auto& s : strs)
{
cout << bf.Test(s) << endl;
}
/*for (auto& s : strs)
{
bf.Set(s+'a');
}*/
for (auto& s : strs)
{
cout << bf.Test(s + 'a') << endl;
}
cout << bf.Test("摆渡") << endl;
cout << bf.Test("百毒") << endl;
}
void TestBloomFilter2()
{
srand(time(0));
const size_t N = 100000;
BloomFilter<N> bf;
std::vector<std::string> v1;
//std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";
//std::string url = "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=65081411_1_oem_dg&wd=ln2&fenlei=256&rsv_pq=0x8d9962630072789f&rsv_t=ceda1rulSdBxDLjBdX4484KaopD%2BzBFgV1uZn4271RV0PonRFJm0i5xAJ%2FDo&rqlang=en&rsv_enter=1&rsv_dl=ib&rsv_sug3=3&rsv_sug1=2&rsv_sug7=100&rsv_sug2=0&rsv_btype=i&inputT=330&rsv_sug4=2535";
std::string url = "猪八戒";
for (size_t i = 0; i < N; ++i)
{
v1.push_back(url + std::to_string(i));
}
for (auto& str : v1)
{
bf.Set(str);
}
// v2跟v1是相似字符串集(前缀一样),但是后缀不一样
std::vector<std::string> v2;
for (size_t i = 0; i < N; ++i)
{
std::string urlstr = url;
urlstr += std::to_string(9999999 + i);
v2.push_back(urlstr);
}
size_t n2 = 0;
for (auto& str : v2)
{
if(bf.Test(str)) // 误判
{
++n2;
}
}
cout << "相似字符串误判率:" << (double)n2 / (double)N << endl;
// 不相似字符串集 前缀后缀都不一样
std::vector<std::string> v3;
for (size_t i = 0; i < N; ++i)
{
//string url = "zhihu.com";
string url = "孙悟空";
url += std::to_string(i + rand());
v3.push_back(url);
}
size_t n3 = 0;
for (auto& str : v3)
{
if (bf.Test(str))
{
++n3;
}
}
cout << "不相似字符串误判率:" << (double)n3 / (double)N << endl;
}
};
代码中的set()函数是我上一篇Bitset中的函数,这里有对Bitset的代码的引用,大家看我Bitset那一篇文章。-------》位图