布隆过滤器是一种空间利用率较高的概率型数据结构,用来测试一个元素是否在集合中。但是存在一定可能,导致结果误判。即元素不在集合中,查询结果却返回元素在集合中。
布隆过滤器一些的性质
布隆过滤器的工作方式
一个空的布隆过滤器是一个由m个二进制位构成的数组。
我们需要k个hash函数来计算输入的hash值。当我们向过滤器中添加一个元素事,k个hash函数计算出的索引值 h1(x), h2(x),… hk(x)被设置位1。例如将geeks加入到过滤器中,我们用到3个hash函数,过滤器的长度为10,初始
值被设置为0。哈希函数的计算结果如下:
h1(“geeks”) % 10 = 1
h2(“geeks”) % 10 = 4
h3(“geeks”) % 10 = 7
接着我们将1,4,7位设置位1。
接着我们将nerd添加到集合中,hash值计算结果如下。
h1(“nerd”) % 10 = 3
h2(“nerd”) % 10 = 5
h3(“nerd”) % 10 = 4
如果我们想检查geeks是否在集合中,我们以相反的方式进行处理。我们使用h1,h2,h3计算初hash值。如果对应的二进制位都位1,那么可以判定“geeks”可能在集合中。如果任何一位为0,那么"geeks"一定不在集合中。
布隆过滤器结果误判
假如我们希望判定cat是否在集合中,首先计算hash值。得到1,3,7。但1,3,7是之前添加其它单词后设置的结果,我们之前并没有添加cat,因此造成了误判。
h1(“cat”) % 10 = 1
h2(“cat”) % 10 = 3
h3(“cat”) % 10 = 7
如果我们希望降低误判率,我们需要使用更多的hash函数及更大的数组。
布隆过滤器的误报概率
假设数组长度为m,并使用k个hash函数,n是要插入到过滤器中的元素个数,那么误报率的计算如下:
位数组的大小
如果过滤器中的元素数量已直,期望的误报率位p,那么二进制位数组大小计算公式如下:
最优哈希函数数量
如果m是数组长度,n是插入的元素个数,k是hash函数的个数,k计算公式如下:
哈希函数的选择
哈希函数独立且生成的数值均匀分布,并且尽可能的快。比如MurmurHash、Jenkins_hash_function。
首先添加依赖,版本可根据实际使用来调整。
<dependency>
<groupId>com.google.guavagroupId>
<artifactId>guavaartifactId>
<version>22.0version>
dependency>
创建布隆过滤器时要传入期望处理的元素数量,及最期望的误报的概率。如下分别是500和0.01。
BloomFilter<Integer> filter = BloomFilter.create(
Funnels.integerFunnel(),
500,
0.01);
接着向过滤器中插入元素。
filter.put(1);
filter.put(2);
filter.put(3);
我们添加了3个元素,并且定义了最大元素数量为500,因此我们的过滤器将会产生非常准确的结果。我们使用mightContain()方法来测试。
assertThat(filter.mightContain(1)).isTrue();
assertThat(filter.mightContain(2)).isTrue();
assertThat(filter.mightContain(3)).isTrue();
assertThat(filter.mightContain(100)).isFalse();
因为布隆过滤器是一种概率型数据结构,因此返回true表示元素有极大的概率存在。当返回false那么表示元素一定不存在。
当我们创建布隆过滤器时,尽可能提供准确的元素数量。否则将会产生较高的误报率。
下面的例子表示集合最多5个元素,这样在实际使用时就会产生很高的误报率。
BloomFilter<Integer> filter = BloomFilter.create(
Funnels.integerFunnel(),
5,
0.01);
IntStream.range(0, 100_000).forEach(filter::put);
[1]布隆过滤器算法介绍,https://www.geeksforgeeks.org/bloom-filters-introduction-and-python-implementation/
[2]Guava中的布隆过滤器,https://www.baeldung.com/guava-bloom-filter