4. 布隆过滤器
4.1 问题引入
假设现在有大量的字符串数据,如何判断哪些字符串已经存在,那些不存在?
- 用哈希表存储用户记录,缺点:浪费空间
- 用位图存储用户记录,缺点:位图一般只能处理整形,如果内容编号是字符串,就无法处理了。
- 将哈希与位图结合,即布隆过滤器
核心就是将 字符串 --字符串哈希–> 整形 --> 映射到一个位图中
判断该字符串在不在?
- 在:是不准确的,可能存在误判。
- 不在:是准确的。
由于1个值映射1个位比较容易误判,所以布隆打算1个值映射多个位,这样误判的概率就会变小
4.2 概念
布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间
相关文章,我们这里将哈希函数设置为3,当有n个值时,布隆过滤器的大小设置为4.3n比较好。
4.3 布隆过滤器的使用场景
在上图中,如果想要精确的结果,可以在判断"该昵称被别人注册的时候",这时去服务器中进行查询(由于布隆过滤器判断 在 是不准确的),来得到准确的结果。这样就可以不查询(过滤掉) “该昵称被别人注册的时候” 的情况,从而加快速度
4.4 布隆过滤器的实现
struct BKDRHash
{
size_t operator()(const string& s)
{
register size_t hash = 0;
for (const auto& e : s) {
hash = hash * 131 + e;
}
return hash;
}
};
struct SDBMHash
{
size_t operator()(const string& s)
{
register size_t hash = 0;
for (const auto& e : s) {
hash = 65599 * hash + e;
}
return hash;
}
};
struct RSHash
{
size_t operator()(const string& s)
{
register size_t hash = 0;
size_t magic = 63689;
for (const auto& e : s) {
hash = hash * magic + e;
magic *= 378551;
}
return hash;
}
};
template<size_t N
, class K = string
, class HashFunc1 = BKDRHash
, class HashFunc2 = SDBMHash
, class HashFunc3 = RSHash>
class BloomFilter
{
public:
void set(const K& key)
{
size_t hash1 = HashFunc1()(key) % N;
size_t hash2 = HashFunc2()(key) % N;
size_t hash3 = HashFunc3()(key) % N;
_bs.set(hash1);
_bs.set(hash2);
_bs.set(hash3);
}
bool test(const K& key)
{
size_t hash1 = HashFunc1()(key) % N;
if (!_bs.test(hash1)) return false;
size_t hash2 = HashFunc2()(key) % N;
if (!_bs.test(hash2)) return false;
size_t hash3 = HashFunc3()(key) % N;
if (!_bs.test(hash3)) return false;
return true;
}
/*
* 一般不支持删除,删除一个值可能会影响其他值
* 非要支持删除,也是可以的,用多个位标记一个值,存引用计数
* 但是这样话,空间消耗的就变大了
*/
void reset(const K& key);
private:
bitset<N> _bs;
};
4.5 应用
给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法
近似算法可以使用布隆过滤器,精确算法可以先使用哈希切割,再将Ai和Bi插入到setA和setB中,这样就可以快速且准确的找交集
此时就会有一个问题,如果某一个小文件还是太大(30GB),该如何处理?有两种情况
- 这个小文件中大多数都是某1个query
- 这个小文件,有很多不同的query
解决方案:不管文件大小,直接读到内存插入到set。如果是情况1,文件很大有很多重复,后面重复插入都失败,可以插入到set中
如果是情况2,不断插入set以后,内存不足,会抛异常,需要换一个哈希函数进行二次切分,再找交集。
给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址?
哈希切割,如果需要统计ip的topK问题,可以使用优先级队列
5.搜索问题
-
暴力查找 数据量大了,效率就低
-
排序+二分查找 问题a:排序有代价 问题b:数组不方便增删
-
搜索树 ->AVL树+红黑树
-
哈希
以上数据结构,空间消耗很高,当需要存储数量很大的数据时
- 整形的在不在及其扩展问题:位图及变形 节省空间
- 其他类型的在不在问题:布隆过滤器