目录
位图概念
位图结构
常用接口函数
set
reset
test
size
Count
位图完整代码实现
布隆过滤器
概念
结构
插入
查找
删除
完整代码
布隆过滤器优点
布隆过滤器缺陷
总结
位图就是用bit位来存放某种状态,适用于海量数据,且无重复数据,一般应用与判断数据在不在。
例:数据是否存在给定的整形数据中,结果是在或不在,两种状态,因此我们可以使用一个bit位来代表是否存在,0代表不存在,1代表存在。
位图底层采用vector实现,这里我们存储char类型,8个bit位为一个单元。
template
class bit_set
{
public:
bit_set()
{
_bits.resize(N / 8 + 1, 0);
}
private:
vector _bits;
};
将该比特位置1,也就是代表该数据存在。
void set(size_t x)
{
size_t i = x / 8;
size_t j = x % 8;
_bits[i] |= 1 << j;
}
将该位置置0,也就是删除该元素。
void reset(size_t x)
{
size_t i = x / 8;
size_t j = x % 8;
_bits[i] &= ~(1 << j);
}
查看该数据是否存在。
bool test(size_t x)
{
size_t i = x / 8;
size_t j = x % 8;
return _bits[i] & (1 << j);
}
返回该位图可容纳的bit位个数
size_t size()const
{
return N + 8;
}
返回该位图中状态为1bit位的个数
size_t Count()const
{
size_t count = 0;
for (auto e : _bits)
{
for (size_t i = 0; i < 8; ++i)
{
if (e & (1 << i))
{
count++;
}
}
}
return count;
}
namespace cxq
{
template
class bit_set
{
public:
bit_set()
{
_bits.resize(N / 8 + 1, 0);
}
void set(size_t x)
{
size_t i = x / 8;
size_t j = x % 8;
_bits[i] |= 1 << j;
}
void reset(size_t x)
{
size_t i = x / 8;
size_t j = x % 8;
_bits[i] &= ~(1 << j);
}
bool test(size_t x)
{
size_t i = x / 8;
size_t j = x % 8;
return _bits[i] & (1 << j);
}
size_t Count()const
{
size_t count = 0;
for (auto e : _bits)
{
for (size_t i = 0; i < 8; ++i)
{
if (e & (1 << i))
{
count++;
}
}
}
return count;
}
size_t size()const
{
return N + 8;
}
private:
vector _bits;
};
}
当我们看短视频时,平台会给我们推送新的视频,一些我们已经看过的视频将不会再次推送,这是如何实现的呢?
服务器记录用户观看的视频记录,通过查找在不在记录内,不在就推送,在就说明已经看过,不再推送。那么如何快速查找呢?
布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概 率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也 可以节省大量的内存空间。
通过将一个值映射到多个bit位,会大大缩小误判率。
因此,我们可以使用多种不同的哈希函数,将一个值映射到多个bit位。
下面介绍三种比较优秀的哈希函数。
struct BKDRHash
{
size_t operator()(const string& s)
{
// BKDR
size_t value = 0;
for (auto ch : s)
{
value *= 31;
value += ch;
}
return value;
}
};
struct APHash
{
size_t operator()(const string& s)
{
size_t hash = 0;
for (long 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 DJBHash
{
size_t operator()(const string& s)
{
size_t hash = 5381;
for (auto ch : s)
{
hash += (hash << 5) + ch;
}
return hash;
}
};
N表示存储的数据个数,N*X表示我们所开的空间大小,所开空间越大,出现冲突的概率越低,误判的概率也越低,空间是最主要影响误判率的。
template
class BloomFilter
{
private:
bitset _bits;
};
将映射的bit位全部置1
void set(const K& key)
{
size_t len = X * N;
size_t index1 = HashFunc1()(key) % len;
size_t index2 = HashFunc2()(key) % len;
size_t index3 = HashFunc3()(key) % len;
_bits.set(index1);
_bits.set(index2);
_bits.set(index3);
}
查看映射的bit位是否都为1,如果有一个不为1就说明一定不存在。
全为1说明可能在,此时可以再到数据库内查找。
bool test(const K& key)
{
size_t len = X * N;
size_t index1 = HashFunc1()(key) % len;
if (!_bits.test(index1))
{
return false;
}
size_t index2 = HashFunc2()(key) % len;
if (!_bits.test(index2))
{
return false;
}
size_t index3 = HashFunc3()(key) % len;
if (!_bits.test(index3))
{
return false;
}
return true;
}
布隆过滤器一般不支持删除,因为删除一个值过后,可能影响映射到与该值相同bit位的值。
如果一定要实现删除,可以采用引用计数思想,我们可以采用多个bit位标记一个值,来代表这个值出现的次数,但是,这样就大大增加了空间消耗,失去了布隆过滤器的空间优势!
//布隆过滤器的思想是一个值映射多个位置,这样误判的概率就会缩小
//主要影响误判率的是所开的空间大小
template
class BloomFilter
{
public:
void set(const K& key)
{
size_t len = X * N;
size_t index1 = HashFunc1()(key) % len;
size_t index2 = HashFunc2()(key) % len;
size_t index3 = HashFunc3()(key) % len;
_bits.set(index1);
_bits.set(index2);
_bits.set(index3);
}
bool test(const K& key)
{
size_t len = X * N;
size_t index1 = HashFunc1()(key) % len;
if (!_bits.test(index1))
{
return false;
}
size_t index2 = HashFunc2()(key) % len;
if (!_bits.test(index2))
{
return false;
}
size_t index3 = HashFunc3()(key) % len;
if (!_bits.test(index3))
{
return false;
}
return true;
}
// 不支持删除,删除可能会影响其他值。
void Reset(const K& key);
//如果非要支持删除,我们可以采用引用计数
//使用多个bit位来标记key所出现的个数
//但是这样会增加空间,丧失布隆过滤器的优势
private:
bitset _bits;
};
- 1. 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关
- 2. 哈希函数相互之间没有关系,方便硬件并行运算
- 3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
- 4. 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势
- 5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
- 6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算
- 1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)
- 2. 不能获取元素本身
- 3. 一般情况下不能从布隆过滤器中删除元素
- 4. 如果采用计数方式删除,可能会存在计数回绕问题
位图:节省空间,效率高。缺点:只能处理整数。
布隆过滤器:适用于数据量大,空间小,允许误判场景,可以处理字符串和自定义类型对象。