位图只能用来快速判断一个整数是否在一堆整数中,如果我们想要判断一个字符串是否在一堆字符串里,那么位图就做不到了,因此布隆过滤器就出现了。
它是由一个很长的二进制向量和一系列随机 映射函数组成,布隆过滤器可以用于检索一个元素是否在一个集合中。那我们可以利用哈希函数计算出它具体的存放位置。
它的优点是空间效率和查询时间都远远超过一般的算法,将这40亿的数据内存由16GB变成500MB,可见其强大。
缺点是有一定的误识别率、不便于删除。布隆过滤器会出现:检测存在,而实际中却不存在。而不会出现:实际中不存在,而检测存在。
布隆过滤器原理
如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间越来越大。同时检索速度也越来越慢。
Bloom Filter 是一种空间效率很高的随机数据结构,Bloom filter 可以看做是对 bit-map 的扩展, 它的原理是:
当一个元素被加入集合时,通过 K 个Hash函数将这个元素映射成一个位阵列(Bit array)中的 K 个点,把它们置为1。检索时,我们只要看看这些点是不是都是 1 就(大约)知道集合中有没有它了:
如果这些点有任何一个 0,则被检索元素一定不在;
如果都是 1,则被检索元素可能在。
如果只是空洞的说这些原理的话,肯定大家都不知道布隆过滤器有什么用处。布隆过滤器对于单机来说可能用处不是很大,但对于分布式来说就比较有用了。
如主从分布:一个数组过来,我想要知道他是不是在内存中,我们是不是需要一个一个去访问磁盘,判断数据是否存在。但是问题来了访问磁盘的速度是很慢的,所以效率会很低,如果使用布隆过滤器,我们就可以先去过滤器这个集合里面找一下对应的位置的数据是否存在。虽然布隆过滤器有他的缺陷,但是我们能够知道的是当前位置为0是肯定不存在的,如果都不存在,就不需要去访问了。
优点:相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数。另外, Hash函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。
布隆过滤器可以表示全集,其它任何数据结构都不能;
k和m相同,使用同一组Hash函数的两个布隆过滤器的交并差运算可以使用位操作进行。
缺点:但是布隆过滤器的缺点和优点一样明显。误算率是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。
另外,一般情况下不能从布隆过滤器中删除元素。我们很容易想到把位列阵变成整数数组,每插入一个元素相应的计数器加1, 这样删除元素时将计数器减掉就可以了。然而要保证安全的删除元素并非如此简单。首先我们必须保证删除的元素的确在布隆过滤器里面. 这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。
在降低误算率方面,有不少工作,使得出现了很多布隆过滤器的变种。
简易的布隆过滤器实现代码:
为了降低误算率的算法
struct _HashFunc1
{
size_t operator()(const string& str)
{
size_t num = 0;
for (int i = 0; i < (int)str.size(); i++)
{
num = num * 131 + str[i];
}
return num;
}
};
struct _HashFunc2
{
size_t operator()(const string& str)
{
size_t num = 0;
for (int i = 0; i < (int)str.size(); i++)
{
num = num * 65599 + str[i];
}
return num;
}
};
struct _HashFunc3
{
size_t operator()(const string& str)
{
size_t magic = 63689;
size_t num = 0;
for (int i = 0; i < (int)str.size(); i++)
{
num = num *magic + str[i];
magic *= 378551;
}
return num;
}
};
struct _HashFunc4
{
size_t operator()(const string& str)
{
size_t num = 0;
for (int i = 0; i < (int)str.size(); i++)
{
if ((i & 1) == 0)
{
num ^= ((num << 7) ^ str[i] ^ (num >> 3));
}
else
{
num ^= (~((num << 11) ^ str[i] ^ (num >> 5)));
}
}
return num;
}
};
struct _HashFunc5
{
size_t operator()(const string& str)
{
if (str.empty())
return 0;
size_t num = 5381;
for (int i = 0; i < (int)str.size(); i++)
{
num = num * 33 ^ str[i];
}
return num;
}
};
template<typename K = string
, class HashFunc1 = _HashFunc1
, class HashFunc2 = _HashFunc2
, class HashFunc3 = _HashFunc3
, class HashFunc4 = _HashFunc4
, class HashFunc5 = _HashFunc5>
class BloomFilter
{
public:
BloomFilter(size_t range)
{
_bitmap.resize(range * 5); //为了减少误判,提高精度,用5个位置来表示一个数
}
void Set(const K& key) //要设置为1,必须将5个位置都设置
{
size_t index1 = HashFunc1()(key) % _bitmap.size();
size_t index2 = HashFunc2()(key) % _bitmap.size();
size_t index3 = HashFunc3()(key) % _bitmap.size();
size_t index4 = HashFunc4()(key) % _bitmap.size();
size_t index5 = HashFunc5()(key) % _bitmap.size();
_bitmap[index1]++;
_bitmap[index2]++;
_bitmap[index3]++;
_bitmap[index4]++;
_bitmap[index5]++;
}
bool ReSet(const K& key) //采用引用计数的方式复位
{
size_t index1 = HashFunc1()(key) % _bitmap.size();
size_t index2 = HashFunc2()(key) % _bitmap.size();
size_t index3 = HashFunc3()(key) % _bitmap.size();
size_t index4 = HashFunc4()(key) % _bitmap.size();
size_t index5 = HashFunc5()(key) % _bitmap.size();
if (_bitmap[index1] == 0 ||
_bitmap[index2] == 0 ||
_bitmap[index3] == 0 ||
_bitmap[index4] == 0 ||
_bitmap[index5] == 0) //只要有一个为0,说明这个key不存在
return false;
//要是都不为0,才减一
_bitmap[index1]--;
_bitmap[index2]--;
_bitmap[index3]--;
_bitmap[index4]--;
_bitmap[index5]--;
return true;
}
bool Test(const K& key)
{
size_t index1 = HashFunc1()(key) % _bitmap.size();
size_t index2 = HashFunc2()(key) % _bitmap.size();
size_t index3 = HashFunc3()(key) % _bitmap.size();
size_t index4 = HashFunc4()(key) % _bitmap.size();
size_t index5 = HashFunc5()(key) % _bitmap.size();
//只有五个位置都为1,才存在
if (_bitmap[index1] != 0 &&
_bitmap[index2] != 0 &&
_bitmap[index3] != 0 &&
_bitmap[index4] != 0 &&
_bitmap[index5] != 0)
return true;
return false;
}
private:
vector<size_t> _bitmap;
};