布隆过滤器是有布隆(Burton Howard Bloom)在1970年提出的一种紧凑型的、比较巧妙的概率型数据结构,特点是高效的插入和查询,可以用来告诉你"某个数据一定不存在或可能存在“,它是多个哈希函数,将一个数据映射到位图结构中,这种方法不仅可以提高查询效率,也可以节省大量的内存空间。
插入原理:
采用n个字符串哈希函数,分别计算字符串的哈希地址并映射到位图对应比特位上置1,即完成插入,相当于利用位图上的n个比特位同时为1,来表示一个数据。
可以看出,以上方法是存在哈希冲突的可能性的,但是可能性较小,且采用的比特位数越多,概率越小。
bool Insert(const K& data) {
size_t index = K2INT1()(data) % _bst.size();
_bst.set(index);
index = K2INT2()(data) % _bst.size();
_bst.set(index);
index = K2INT3()(data) % _bst.size();
_bst.set(index);
index = K2INT4()(data) % _bst.size();
_bst.set(index);
index = K2INT5()(data) % _bst.size();
_bst.set(index);
++_size;
return true;
}
分别计算每个哈希值,判断对应的比特位是否为1,只要有一个位置是0,则说明该元素一定不在哈希表中。
注意:
即使每个位置都为1,只能说明可能存在哈希表中,不能一定说明存在。
bool Find(const K& data) {
size_t index = K2INT1()(data) % _bst.size();
if (!_bst.test(index)) return false;
index = K2INT2()(data) % _bst.size();
if (!_bst.test(index)) return false;
index = K2INT3()(data) % _bst.size();
if (!_bst.test(index)) return false;
index = K2INT4()(data) % _bst.size();
if (!_bst.test(index)) return false;
index = K2INT5()(data) % _bst.size();
if (!_bst.test(index)) return false;
//只能说明可能存在
return true;
}
布隆过滤器不能直接支持删除工作,因为一个元素在比特位上可能与其他元素有重叠,在删除一个元素时,可能会影响其他元素。
如何支持删除操作:
若想支持删除操作,则底层不能再使用位图,因为比特位只能表示两种状态,可能会与其他元素形成重叠。所以可以通过底层采用整形数组的形式,在插入时,给对应位置+1,删除时给对应位置-1,这样在删除一个元素时,就不会影响其他元素。
缺陷:同样无法确定元素一定在布隆过滤器中;增加了几倍的空间;存在计算回绕问题。
(1)增加和查询元素的时间复杂度为O(K),(K为哈希函数的个数,一般较小),与数据量大小无关。
(2)哈希函数互相之间没有关系,方便硬件并行计算。
(3)布隆过滤器不需要存储元素本身,在某些对数据保密性要求比较严格的场景有较大优势。
(4)在能够承受一定的误判时,布隆过滤器比其他数据结构有很大的空间优势。
(5)数据量很大时,布隆过滤器可以表示全集,而其他数据结构不能。
(6)使用同一组散列函数的布隆过滤器可以进行交、并、差集运算。
(1)有误判率,即不能准确判断元素是否在集合中。
(2)不能获取元素本身。
(3)一般情况下,不能从布隆过滤器中删除元素。
(4)如果采用计算方式删除,可能存在计算回绕问题。
#pragma once
//将类型转换为整形
#include
template
class T2INT {//int-->int
public:
size_t operator()(const T& data) {
return data;
}
};
class Str2INT {//string-->int
public:
size_t operator()(const std::string& str) {
return SDBMHash(str.c_str());
}
unsigned int SDBMHash(const char* str) {
unsigned int hash = 0;
unsigned int seed = 131;
while (*str) {
hash = hash * seed + (*str++);
}
return (hash & 0x7FFFFFFF);
}
};
//增容质数表
const int PRIMECOUNT = 31;
const size_t primeList[PRIMECOUNT] =
{
5,11, 23,
53ul, 97ul, 193ul, 389ul, 769ul,
1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
1610612741ul, 3221225473ul, 4294967291ul
};
size_t GetNextPrime(size_t prime) {
for (size_t i = 0; i < PRIMECOUNT; ++i) {
if (primeList[i] > prime) {
return primeList[i];
}
}
return primeList[PRIMECOUNT - 1];
}
/*五种字符串转整形的哈希算法*/
class Str2INT1 {
public:
size_t operator()(const std::string& str) {
return SDBMHash(str.c_str());
}
/// @brief SDBM Hash Function
/// @detail 本算法是由于在开源项目SDBM(一种简单的数据库引擎)中被应用而得名,它与BKDRHash思想一致,只是种子不同而已。
size_t SDBMHash(const char* str)
{
register size_t hash = 0;
while (size_t ch = (size_t)*str++)
{
hash = 65599 * hash + ch;
//hash = (size_t)ch + (hash << 6) + (hash << 16) - hash;
}
return hash;
}
};
class Str2INT2 {
public:
size_t operator()(const std::string& str) {
return RSHash(str.c_str());
}
/// @brief RS Hash Function
/// @detail 因Robert Sedgwicks在其《Algorithms in C》一书中展示而得名。
size_t RSHash(const char* str)
{
register size_t hash = 0;
size_t magic = 63689;
while (size_t ch = (size_t)*str++)
{
hash = hash * magic + ch;
magic *= 378551;
}
return hash;
}
};
class Str2INT3 {
public:
size_t operator()(const std::string& str) {
return APHash(str.c_str());
}
/// @brief AP Hash Function
/// @detail 由Arash Partow发明的一种hash算法。
size_t APHash(const char* str)
{
register size_t hash = 0;
size_t ch;
for (long i = 0; ch = (size_t)*str++; i++)
{
if ((i & 1) == 0)
{
hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
}
else
{
hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
}
}
return hash;
}
};
class Str2INT4 {
public:
size_t operator()(const std::string& str) {
return JSHash(str.c_str());
}
/// @brief JS Hash Function
/// 由Justin Sobel发明的一种hash算法。
size_t JSHash(const char* str)
{
if (!*str) // 这是由本人添加,以保证空字符串返回哈希值0
return 0;
register size_t hash = 1315423911;
while (size_t ch = (size_t)*str++)
{
hash ^= ((hash << 5) + ch + (hash >> 2));
}
return hash;
}
};
class Str2INT5 {
public:
size_t operator()(const std::string& str) {
return DEKHash(str.c_str());
}
/// @brief DEK Function
/// @detail 本算法是由于Donald E. Knuth在《Art Of Computer Programming Volume 3》中展示而得名。
size_t DEKHash(const char* str)
{
if (!*str) // 这是由本人添加,以保证空字符串返回哈希值0
return 0;
register size_t hash = 1315423911;
while (size_t ch = (size_t)*str++)
{
hash = ((hash << 5) ^ (hash >> 27)) ^ ch;
}
return hash;
}
};
#pragma once
#include
#include
namespace MyBitSet {
template
class bitset {
public:
/*N是比特位数,右移3位,即除以8,转化为字节为单位
+1防止传入的位数小于8,右移后为0*/
bitset()
: _bst((N >> 3) + 1)
, _count(0)
{}
void set(size_t which) {//比特位置1
assert(which < N);
size_t whichByte = which / 8;//计算处于那个字节
size_t whichBite = which % 8;//计算处于该字节的那一个比特位
if (_bst[whichByte] & (1 << whichBite)) {//说明该元素已经存在
return;
}
_bst[whichByte] |= 1 << whichBite;
++_count;//比特位为1的个数增加
}
void reset(size_t which) {//比特位置0
assert(which < N);
size_t whichByte = which / 8;//计算处于那个字节
size_t whichBite = which % 8;//计算处于该字节的那一个比特位
if (test(which)) {//该比特位是1,才进行置0
_bst[whichByte] ^= 1 << whichBite;
--_count;//比特位为1的个数减少
}
}
bool test(size_t which) const {//检测比特位是否为1
assert(which < N);
size_t whichByte = which / 8;//计算处于那个字节
size_t whichBite = which % 8;//计算处于该字节的那一个比特位
return 0 != (_bst[whichByte] & (1 << whichBite));
}
size_t size() const {//返回比特位位数
return N;
}
size_t count() const {//返回比特位为1的个数
/*size_t num = 0;
for (size_t i = 0; i < N; ++i) {
if (test(i)) ++num;
}
return num;
若比特位数较多,遍历则效率太低
*/
return _count;
}
private:
std::vector _bst;
size_t _count;//记录比特位为1的个数
};
}
#pragma once
#include "BitSet.hpp"
#include "Common.h"
//采用五个比特位,来映射一个数据
template
class BloomFilter {
public:
BloomFilter()
: _bst()
, _size(0)
{}
bool Insert(const K& data) {
size_t index = K2INT1()(data) % _bst.size();
_bst.set(index);
index = K2INT2()(data) % _bst.size();
_bst.set(index);
index = K2INT3()(data) % _bst.size();
_bst.set(index);
index = K2INT4()(data) % _bst.size();
_bst.set(index);
index = K2INT5()(data) % _bst.size();
_bst.set(index);
++_size;
return true;
}
bool Find(const K& data) {
size_t index = K2INT1()(data) % _bst.size();
if (!_bst.test(index)) return false;
index = K2INT2()(data) % _bst.size();
if (!_bst.test(index)) return false;
index = K2INT3()(data) % _bst.size();
if (!_bst.test(index)) return false;
index = K2INT4()(data) % _bst.size();
if (!_bst.test(index)) return false;
index = K2INT5()(data) % _bst.size();
if (!_bst.test(index)) return false;
//只能说明可能存在
return true;
}
size_t Size() const {
return _size;
}
private:
MyBitSet::bitset _bst;
size_t _size;
};