目录
前文
一,为什么有布隆过滤器
二,什么是布隆过滤器
三,布隆过滤器的实现
四,布隆过滤器的优缺点
4.1 布隆过滤器的优点
4.2 布隆过滤器的缺点及其改进方式
4.2.1 查找误判及其改进方式分析
4.2.2 不能删除以及改进方式分析
总结
前文
本文主要讲解一下针对大数据字符串的处理——布隆过滤器,本文主要带领大家深入了解一下布隆过滤器的基本使用,优缺点分析,应用场景,应用案例
一,为什么有布隆过滤器
在我们的日常生活中,关于字符串类型的大数据搜索不在少数,例如游戏注册时,当你输入账号名字时,服务器就需要将账号名字与存储名字的数据库作对比,如果数据库里有,则当前名字已经被使用,则需要重新输入账号。
像上面这种数据库中可能有上亿或者几十亿的数据,用哈希表比对的话,数据都加载不到内存里,而上节内容讲的位图也只能处理整形类型,因此就需要引入新的数据结构——布隆过滤器来处理.
二,什么是布隆过滤器
布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。
首先字符串的映射有一个不可避免的问题:就是字符串的组合过多,如果映射一个位置,极有可能造成冲突,也就是多个不同字符串映射到同一字符串。
而我们的布隆过滤器对次的解决思路是:一个字符串通过不同的哈希函数,映射多个位置,借此来减少冲突,注意是减少冲突,哈希映射的话,字符串的冲突是无法绝对避免的,但是通过这种方法,可以极大可能的减少冲突
有的小伙伴此时可能就会提出疑问,是不是映射的位置越多,冲突的可能性越少?
这个问题可以看一下下面知乎大佬的文章,文章中对布隆过滤器数学分析有详细的描写
详解布隆过滤器的原理,使用场景和注意事项 - 知乎
三,布隆过滤器的实现
对于布隆过滤器的实现,我们以前文的位图为基础进行封装,但是这里要开的空间和位图有些不一样。
上图中是知乎大佬推出的公式,k为哈希函数的数量,m为布隆过滤器空间大小,n为插入元素个数,p为误报率,我们这里选择的哈希函数为3个,根据公式计算可得,想要误报率保持偏低的水准,至少满足下面的比例
4.3n=m
因此我们在开辟空间时,需要开辟4.3*N个比特的空间
具体的代码如下
struct BKDRHash
{
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;
}
};
template<size_t N, class K = string, class Hash1=BKDRHash, class Hash2=APHash, class Hash3=DJBHash>
class BloomFilter
{
public:
void set(const K& key)
{
int len = N * _x;
//根据三个字符串转换size_t的函数,求三个映射位置并且插入
size_t hash1 = Hash1()(key) % len;
_bs.set(hash1);
size_t hash2 = Hash2()(key) % len;
_bs.set(hash2);
size_t hash3 = Hash3()(key) % len;
_bs.set(hash3);
}
bool test(const K& key)
{
int len = N * _x;
size_t hash1 = Hash1()(key) % len;
size_t hash2 = Hash2()(key) % len;
size_t hash3 = Hash3()(key) % len;
//三个映射位置都不为空才存在
if (_bs.test(hash1)
&& _bs.test(hash2)
&& _bs.test(hash3))
{
return true;
}
return false;
}
private:
static const size_t _x = 5;
BitSet<N* _x> _bs;
};
上面的三个哈希函数,是我们在网上找的效率比较高且有效的哈希函数,铁子们也可以用其他的哈希函数。
四,布隆过滤器的优缺点
4.1 布隆过滤器的优点
1. 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关
2. 哈希函数相互之间没有关系,方便硬件并行运算
3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
4. 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势
5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算
4.2 布隆过滤器的缺点及其改进方式
最主要的缺点就是1.查找可能有误判,2.以及不能删除
4.2.1 查找误判及其改进方式分析
首先为什么会有查找误判呢?
在查找的时候映射的所有位置都不为0,则说明其有可能存在,有一个为0则说明数据一定不存在,为什么说都不为0可能存在呢,我们看一下下面的图
如上图所示,我们并为插入"饿了么",但是由于其映射的位置刚和和其他多个字符映射的位置重叠,此时查找,布隆过滤器会告诉我们该元素存在,但实际上却不存在
那么有什么改进的方式吗?
进行二次判断,当我们先用布隆过滤器进行查找,如果返回存在,我们在遍历数据库查找该数据,这样和原来直接遍历数据库查找相比,布隆过滤器会帮助我们过滤掉大部分的数据(这也是其过滤意义所在),剩下的小部分数据可能需要到数据库中查找,这样大大减少了我们遍历数据库的次数,极大的提高了效率
4.2.2 不能删除以及改进方式分析
布隆过滤器不能直接删除,因为在删除一个元素时,可能会影响其他元素.(注意布隆过滤器是主要功能用来搜索的,不是用来增删查改的)
当然,如果非要删除自然也有办法:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储空间的代价来增加删除操作。
上面的删除方法也有缺陷
1. 无法确认元素是否真正在布隆过滤器中
2. 存在计数回绕
总结
位图和布隆过滤器是专门用来处理大数据文件搜索问题的,他们性能的高效和哈希表相比要高很多,但是各自也有使用场景的限制,铁子们使用时还是应该分析应用场景选择合适的数据结构