介绍
在我们的常用数据结构中,map或set可用于保存一个集合。这两种数据结构虽然简便,空间上的效率却不是特别高。作为基础数据结构,实现上他们一般采用的是(红黑)树或者普通哈希表。考虑下面这种情形:
- 集合元素数量很大
- 我们只希望确认集合中是否存在某个元素,而不关心该元素的具体值(相对map结构而言)
- 可以接受一定程度的误判率(元素有时候并未存在于该集合之中)
如果同时满足上述几个条件,那么布隆过滤器就可作为你的候选数据结构之一。布隆过滤器中保存了一段连续bit,利用哈希算法将集合中每个元素的key映射到这段连续bit上(若干个bit位赋为1)。当验证一个key是否存在于集合中时,只需要在为该key应用同样的哈希算法,然后检查布隆过滤器中相应bit位的值是否全都为1即可。接下来然我们参考LevelDB的源代码来一探布隆过滤器的实际例子吧
布隆过滤器示例
util/bloom.cc
void CreateFilter(const Slice* keys, int n, std::string* dst) const override {
// Compute bloom filter size (in both bits and bytes)
size_t bits = n * bits_per_key_;
// For small n, we can see a very high false positive rate. Fix it
// by enforcing a minimum bloom filter length.
if (bits < 64) bits = 64;
size_t bytes = (bits + 7) / 8;
bits = bytes * 8;
const size_t init_size = dst->size();
dst->resize(init_size + bytes, 0);
dst->push_back(static_cast<char>(k_)); // Remember # of probes in filter
char* array = &(*dst)[init_size];
for (int i = 0; i < n; i++) {
// Use double-hashing to generate a sequence of hash values.
// See analysis in [Kirsch,Mitzenmacher 2006].
uint32_t h = BloomHash(keys[i]);
const uint32_t delta = (h >> 17) | (h << 15); // Rotate right 17 bits
for (size_t j = 0; j < k_; j++) {
const uint32_t bitpos = h % bits;
array[bitpos / 8] |= (1 << (bitpos % 8));
h += delta;
}
}
}
上面的函数创建一个布隆过滤器,为n个keys创建一段连续的bit,每个key预留bits_per_key_个bit(n个key全映射为bit后,很可能不会将布隆过滤器的bit空间全部占满,因为映射的结果一般都存在碰撞)。
为了减少映射计算次数(同时降低碰撞频率),我们将实际映射的bit位数从bits_per_key_降低到k = bits_per_key_ * 0.69。
映射函数名称为BloomHash,它将一个key映射为一个32 bit的无符号整数h。之后将h的bit向右轮转17位,取得一个bit的下标,放入布隆过滤器对应位置的bit位,如此循环进行k_次就得到了key的k_个bit位。函数BloomHash的实现在util/hash.cc中,感兴趣的读者可以自行参考。这里想要强调的是,布隆过滤器并没有限定映射的方法,我们可以根据实际需要采用适当的哈希函数,包括多重哈希函数。
接下来,我们看一下如何利用布隆过滤器判断集合中是否存在某个key。
bool KeyMayMatch(const Slice& key, const Slice& bloom_filter) const override {
...
...
uint32_t h = BloomHash(key);
const uint32_t delta = (h >> 17) | (h << 15); // Rotate right 17 bits
for (size_t j = 0; j < k; j++) {
const uint32_t bitpos = h % bits;
if ((array[bitpos / 8] & (1 << (bitpos % 8))) == 0) return false;
h += delta;
}
return true;
}
不难发现,寻找一个key的过程与将key映射为bit位的过程完全对应,只需要判断布隆过滤器中相应的bit位(共k_个)是否全部值都为1。
总结
布隆过滤器是一种空间效率非常高的集合表示方法,它的基本储存单位是bit位。当存储空间有限而数据量相对比较大时,或者希望为链表/数组等数据结构提高查询效率时,可以考虑下采用布隆过滤器。

浙公网安备 33010602011771号