1 布隆过滤器提出
一些场景下面,有大量数据需要判断是否存在,而这些数据不是整形,那么位图就不能使用了:
我们可以用hash 函数先将它转成整形值,再去映射对应的一个位。
但是如果有一个字符串C与A映射的是同一个位, 就会造成误判,C明明不在却被标志成了1.
所以我们可以得出判断一个值key在是不准确的,判断一个值key不在是准确的。(哈希冲突是解决不了的,只能降低)
布隆过滤器的思路就是把key先映射转成哈希整型值,再映射一个位,如果只映射一个位的话,冲突率会比较多,所以可以通过多个哈希函数映射多个位,降低冲突率。
(有一个位置不在,那他就是不在的)
布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你“某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。
布隆过滤器这里跟哈希表不一样,它无法解决哈希冲突的,因为他压根就不存储这个值,只标记映射的位。它的思路是尽可能降低哈希冲突。
2 布隆过滤器的插入 查找
布隆过滤器的思想是将一个元素用多个哈希函数映射到一个位图中,因此被映射到的位置的比特位一定为1。所以可以按照以下方式进行查找:分别计算每个哈希值对应的比特位置存储的是否为零,只要有一个为零,代表该元素一定不在哈希表中,否则可能在哈希表中。
注意:布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,因为有些哈希函数存在一定的误判。
struct HashFuncBKDR
{
// @detail 本 算法由于在Brian Kernighan与Dennis Ritchie的《The CProgramming Language》
// 一书被展示而得 名,是一种简单快捷的hash算法,也是Java目前采用的字符串的Hash算法累乘因子为31。
size_t operator()(const std::string& s)
{
size_t hash = 0;
for (auto ch : s)
{
hash *= 31;
hash += ch;
}
return hash;
}
};
struct HashFuncAP
{
// 由Arash Partow发明的一种hash算法。
size_t operator()(const std::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
{
// 由Daniel J. Bernstein教授发明的一种hash算法。
size_t operator()(const std::string& s)
{
size_t hash = 5381;
for (auto ch : s)
{
hash = hash * 33 ^ ch;
}
return hash;
}
};
template<size_t n,
size_t x = 3,
class k = string,
class Hash1 = HashFuncBKDR,
class Hash2 = HashFuncAP,
class Hash3 = HashFuncDJB >
class Bloomfilter
{
public:
void set(const k& str)
{
int i = Hash1()(str) % M;
_bs.set(i);
i = Hash2()(str) % M;
_bs.set(i);
i = Hash3()(str) % M;
_bs.set(i);
}
bool rest(const k& str)
{
int i = Hash1()(str) % M;
if (!_bs.rest(i))
{
return false;
}
i = Hash2()(str) % M;
if (!_bs.rest(i))
{
return false;
}
i = Hash3()(str) % M;
if (!_bs.rest(i))
{
return false;
}
return true;
}
private:
static const size_t M = n * x;
bit::bitset<n* x> _bs;
};
3 布隆过滤器删除
布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其他元素。
如下图:(如果将 猪八戒 删除,则可能 孙悟空 也被删除了,因为这两个元素在多个哈希函数计算出的比特位上刚好有重叠)
一种支持删除的方法:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储空间的代价来增加删除操作。
缺陷:
- 1. 无法确认元素是否真正在布隆过滤器中
- 2. 存在计数回绕
4 布隆过滤器优点
- 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关
- 哈希函数相互之间没有关系,方便硬件并行运算
- 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
- 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势
- 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
- 使用同一组散列函数的布隆过滤器可以进行交、并、差运算
5 布隆过滤器缺陷
- 有误判率,即存在假阳性,即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)
- 不能获取元素本身
- 一般情况下不能从布隆过滤器中删除元素
- 如果采用计数方式删除,可能会存在计数回绕问题
6 海量数据处理
(以下两张图出自 西安比特教育科技)
1. 给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?
分析:假设平均每个query字符串50byte,100亿个query就是5000亿byte,约等于500G(1G约等于10亿多Byte)
哈希表/红黑树等数据结构肯定是无能为力的。
解决方案1:这个首先可以用布隆过滤器解决,一个文件中的query放进布隆过滤器,另一个文件依次查找,在的就是交集,问题就是找到交集不够准确,因为在的值可能是误判的,但是交集一定被找到了。
解决方案2:
哈希切分,首先内存的访问速度远大于硬盘,大文件放到内存搞不定,那么我们可以考虑切分为小文件,再放进内存处理。
可以利用哈希切分,依次读取文件中query:i=HashFunc(query)%N,N为准备切分多少分小文件,N取决于切成多少份,内存能放下,query放进第i号小文件,这样A和B中相同的query算出的hash值i是一样的,相同的query就进入的编号相同的小文件就可以编号相同的文件直接找交集,不用交叉找,效率就提升了。
(500个G的数据切成1000份,每一份数据500M)
本质是相同的query在哈希切分过程中,一定进入的同一个小文件Ai和Bi,不可能出现A中的的query进入Ai,但是B中的相同query进入了和Bj的情况,所以对Ai和Bi进行求交集即可,不需要Ai和Bj求交集。
哈希切分的问题就是每个小文件不是均匀切分的,可能会导致某个小文件很大内存放不下。(可能哈希切分出来的A1中还有大量的数据),分析一下某个小文件很大有两种情况:1.这个小文件中太部分是同一个query。2.这个小文件是有很多的不同query构成,本质是这些query冲突了。针对情况1,其实放到内存的set中是可以放下的,因为set是去重的。针对情况2,需要换个哈希函数继续二次哈希切分。所以本体我们遇到大于1G小文件,可以继续读到set中找交集,若setinsert时抛出了异常(set插入数据抛异常只可能是申请内存失败了,不会有其他情况),那么就说明内存放不下是情况2,换个哈希函数进行二次哈希切分后再对应找交集。
2.给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址?与上题条件相同,如何找到top K的IP?
感谢大家的观看!