布隆过滤器(Bloom Filter)由Burton Howard Bloom在1970年提出,它实际上是一个很长的二进制向量(位数组) 和一系列 随机映射函数(哈希)。布隆过滤器可以用于检索一个元素是否在一个集合中,是一种空间查询率高的概率型数据结构。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
它专门用来检测集合中是否存在特定的元素。在这里听起来很平常的需求,所以为什么要使用这种数据结构?
我们先来看几个比较常见的例子:
这几个例子有一个共同的特点:如何判断一个元素是否存在一个集合中?
常规思路:
虽然上面描述的这几种数据结构配合常见的排序、二分搜索可以快速高效的处理绝大部分判断元素是否存在集合中的需求。但是当集合里面的元素数量足够大,如果有500W条记录甚至1亿条记录呢?这个时候常规的数据结构的问题就凸显出来了。数组、链表、树等数据结构会存储元素的内容,一旦数据量过大,消耗的内存也会呈现线性增长,最终达到瓶颈。
有的同学可能会问,哈希表不是效率很高吗?查询效率可以达到O(1)。但是哈希表需要消耗的内存依然很高。使用哈希表存储1亿个垃圾email地址的消耗?哈希表的做法:首先,哈希函数将一个email地址映射成8字节信息指纹;考虑到哈希表存储效率通常小于50%(哈希冲突);因此消耗的内存:8 * 2 * 1亿 字节 = 1.6G内存,普查计算机是无法提供如此大的内存。这个时候,布隆过滤器(Bloom Filter)就应运而生。
在理解布隆过滤器的原理时,先来回顾一下哈希函数的知识。
哈希函数
哈希函数的概念是:将任意大小的数据转换成特定大小的数据的函数,转换后的数据称为哈希值或哈希编码。
对于哈希表来说它不仅有哈希函数来得到这么一个index
值,且它会把整个要存的元素全部都放到哈希表里面取,这是一个没有误差的数据结构且有多少的元素,每个元素有多大,那么所有的这些元素需要占的内存空间在哈希表里面都要找相应的内存的大小给存进来。
那么在很多时候我们在工业级应用的时候,发现我们并不需要存所有元素本身,而只需要存一个信息,就是说这个元素在我这个表里面到底是有还是没有。在这种情况下如果只要查询有还是没有的时候,这个时候我们就需要一种更高效的数据结构,更高效的数据结构可以导致的一个结果,就这里有很多元素要存的话,但是我们这个表的话所需要的内存空间很少,同时的话我们不需要把元素本身全部存起来,我们只需要说把这个东西到底是有还是没有。接下来我们看这个数据结构到底是怎么设计出来的。
布隆过滤器(Bloom Filter)的核心实现是一个超大的位数组和几个哈希函数。假设位数组的长度为 m
,哈希函数的个数为k
。
具体的操作流程:假设集合里面有3个元素 {x,y,z}
, 哈希函数的个数为3。
W
元素是否存在集合中的时候,同样的方法将W通过哈希表映射到位数组上的3个点。如果3个点的其中有一个点不为1,则可以判断该元素一定不存在集合中。反之,如果3个点都为1,则该元素可能存在集合中。注意:此处不能判断该元素是否一定存在集合中,可能存在一定的误判率。可以从图中看到:假设某个元素通过映射对应下标为4,5,6这3个点。虽然这3个点都为1,但是很明显这3个点是不同元素经过哈希表得到的位置,因此这种情况说明元素虽然不在集合中,也可能对应的都是1,这就是误判率存在的原因。
常见案例
布隆过滤器思路比较简单,但是对于布隆过滤器的随机映射函数设计,需要计算几次,向量长度设置多少比较合适,这个才是需要认真讨论的。
布隆过滤器添加元素
布隆过滤器查询元素
java实现
//Java
public class BloomFilter {
private static final int DEFAULT_SIZE = 2 < private static final int[] seeds = new int[] { 5, 7, 11, 13, 31, 37, 61 };
private BitSet bits = new BitSet(DEFAULT_SIZE);
private SimpleHash[] func = new SimpleHash[seeds.length];
public BloomFilter() {
for (int i = 0; i func[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]);
}
}
public void add(String value) {
for (SimpleHash f : func) {
bits.set(f.hash(value), true);
}
}
public boolean contains(String value) {
if (value == null) {
return false;
}
boolean ret = true;
for (SimpleHash f : func) {
ret = ret && bits.get(f.hash(value));
}
return ret;
}
// 内部类,simpleHash
public static class SimpleHash {
private int cap;
private int seed;
public SimpleHash(int cap, int seed) {
this.cap = cap;
this.seed = seed;
}
public int hash(String value) {
int result = 0;
int len = value.length();
for (int i = 0; i result = seed * result + value.charAt(i);
}
return (cap - 1) & result;
}
}
}
Python实现
# Python
from bitarray import bitarray
import mmh3
class BloomFilter:
def __init__(self, size, hash_num):
self.size = size
self.hash_num = hash_num
self.bit_array = bitarray(size)
self.bit_array.setall(0)
def add(self, s):
for seed in range(self.hash_num):
result = mmh3.hash(s, seed) % self.size
self.bit_array[result] = 1
def lookup(self, s):
for seed in range(self.hash_num):
result = mmh3.hash(s, seed) % self.size
if self.bit_array[result] == 0:
return "Nope"
return "Probably"
bf = BloomFilter(500000, 7)
bf.add("dantezhao")
print (bf.lookup("dantezhao"))
print (bf.lookup("yyj"))
C/C++实现
C/C++
#include
#include
#include
using namespace std;
typedef unsigned int uint;
const int DEFAULT_SIZE = 1
class BloomFilter {
public:
BloomFilter() : hash_func_count(3) {}
BloomFilter(int bitsize, int str_count) {
hash_func_count = ceil((bitsize / str_count) * log(2));
}
~BloomFilter() {}
uint RSHash(const char *str, int seed);
void Add(const char *str);
bool LookUp(const char *str);
private:
int hash_func_count;
bitset bits;
};
uint BloomFilter::RSHash(const char *str, int seed) {
uint base = 63689;
uint hash = 0; while (*str) { hash = hash * base + (*str++);
base *= seed;
} return (hash & 0x7FFFFFFF);
}
void BloomFilter::Add(const char* str) {
int index = 0;for(int i = 0; i index = static_cast(RSHash(str, seed[i])) % DEFAULT_SIZE;
bits[index] = 1;
}return ;
}
bool BloomFilter::LookUp(const char* str) {
int index = 0;for(int i = 0; i index = static_cast(RSHash(str, seed[i])) % DEFAULT_SIZE;if (!bits[index]) return false;
}return true;
}
javascript实现
// JavaScript
class BloomFilter {
constructor(maxKeys, errorRate) {
this.bitMap = [];
this.maxKeys = maxKeys;
this.errorRate = errorRate;
// 位图变量的长度,需要根据maxKeys和errorRate来计算
this.bitSize = Math.ceil(maxKeys * (-Math.log(errorRate) / (Math.log(2) * Math.log(2))));
// 哈希数量
this.hashCount = Math.ceil(Math.log(2) * (this.bitSize / maxKeys));
// 已加入元素数量
this.keyCount = 0;
}
bitSet(bit) {
let numArr = Math.floor(bit / 31);
let numBit = Math.floor(bit % 31);
this.bitMap[numArr] |= 1 < }
bitGet(bit) {
let numArr = Math.floor(bit / 31);
let numBit = Math.floor(bit % 31);
return (this.bitMap[numArr] &= 1 < }
add(key) {
if (this.contain(key)) {
return -1;
}
let hash1 = MurmurHash(key, 0, 0),
hash2 = MurmurHash(key, 0, hash1);
for (let i = 0; i this.bitSet(Math.abs(Math.floor((hash1 + i * hash2) % this.bitSize)));
}
this.keyCount++;
}
contain(key) {
let hash1 = MurmurHash(key, 0, 0);
let hash2 = MurmurHash(key, 0, hash1);
for (let i = 0; i if (!this.bitGet(Math.abs(Math.floor((hash1 + i * hash2) % this.bitSize)))) {
return false;
}
}
return true;
}
}
/**
* MurmurHash
*
* 参考 http://murmurhash.googlepages.com/
*
* data:待哈希的值
* offset:
* seed:种子集
*
*/
function MurmurHash(data, offset, seed) {
let len = data.length,
m = 0x5bd1e995,
r = 24,
h = seed ^ len,
len_4 = len >> 2;
for (let i = 0; i let i_4 = (i < k = data[i_4 + 3];
k = k < k = k | (data[i_4 + 2] & 0xff);
k = k < k = k | (data[i_4 + 1] & 0xff);
k = k < k = k | (data[i_4 + 0] & 0xff);
k *= m;
k ^= k >>> r;
k *= m;
h *= m;
h ^= k;
}
// avoid calculating modulo
let len_m = len_4 < left = len - len_m,
i_m = len_m + offset;
if (left != 0) {
if (left >= 3) {
h ^= data[i_m + 2] < }
if (left >= 2) {
h ^= data[i_m + 1] < }
if (left >= 1) {
h ^= data[i_m];
}
h *= m;
}
h ^= h >>> 13;
h *= m;
h ^= h >>> 15;
return h;
}
let bloomFilter = new BloomFilter(10000, 0.01);
bloomFilter.add("abcdefgh");
console.log(bloomFilter.contain("abcdefgh"));
console.log(bloomFilter.contain("abcdefghi"));
附:其它实现
[布隆过滤器 Python 实现示例](https://www.geeksforgeeks.org/bloom-filters-introduction-and-python-implementation/)
[高性能布隆过滤器 Python 实现示例](https://github.com/jhgg/pybloof)
[布隆过滤器 Java 实现示例 1](https://github.com/lovasoa/bloomfilter/blob/master/src/main/java/BloomFilter.java)
[布隆过滤器 Java 实现示例 2](https://github.com/Baqend/Orestes-Bloomfilter)
部分图片来源于网络,版权归原作者,侵删。
?点击阅读原文,查看往期内容!
快留言?和我互动吧~