Guava中Bloom过滤器
1. 概述
在本文中,我们将研究来自Guava库的Bloom 过滤器 构造。Bloom 过滤器 是一种内存效率高的概率数据结构,我们可以用它来回答给定元素是否在集合中的问题。
Bloom 过滤器没有假阴性,所以当它返回false时,我们可以 100% 确定该元素不在集合中。
但是,Bloom 过滤器可能会返回误报,因此当它返回true时,该元素很有可能在集合中,但我们不能 100% 确定。
要更深入地分析Bloom 过滤器的工作原理,您可以阅读本教程 。
2. Maven依赖
我们将使用 Guava 的 Bloom 过滤器实现,所以让我们添加guava依赖项:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>
最新版本可以在Maven Central 上找到。
3. 为什么使用Bloom 过滤器?
Bloom 过滤器被设计为节省空间和快速。使用它时,我们可以**指定我们可以接受的误报响应的概率,**并且根据该配置,Bloom 过滤器将占用尽可能少的内存。
由于这种空间效率,即使对于大量元素,**Bloom 过滤器也很容易适应内存。**一些数据库,包括 Cassandra 和 Oracle,在进入磁盘或缓存之前使用此过滤器作为第一个检查,例如,当对特定 ID 的请求进入时。
如果过滤器返回 ID 不存在,则数据库可以停止进一步处理请求并返回给客户端。否则,它会转到磁盘并返回如果在磁盘上找到元素。
4. 创建Bloom 过滤器
假设我们要为多达 500 个整数创建一个Bloom 过滤器,并且我们可以容忍百分之一 (0.01) 的误报概率。
我们可以使用Guava库中的BloomFilter类来实现这一点。我们需要传递我们期望插入过滤器的元素数量和所需的误报概率:
BloomFilter<Integer> filter = BloomFilter.create(
Funnels.integerFunnel(),
500,
0.01);
现在让我们在过滤器中添加一些数字:
filter.put(1);
filter.put(2);
filter.put(3);
我们只添加了三个元素,并且我们定义了最大插入次数为 500,因此我们的 Bloom 过滤器应该会产生非常准确的结果。让我们使用*mightContain()*方法对其进行测试:
assertThat(filter.mightContain(1)).isTrue();
assertThat(filter.mightContain(2)).isTrue();
assertThat(filter.mightContain(3)).isTrue();
assertThat(filter.mightContain(100)).isFalse();
顾名思义,当方法返回true时,我们不能 100% 确定给定元素实际上在过滤器中。
在我们的示例中,当mightContain()返回true时,我们可以 99% 确定该元素在过滤器中,并且结果为误报的概率为 1%。当过滤器返回false时,我们可以 100% 确定该元素不存在。
5. 过度饱和的Bloom 过滤器
当我们设计我们的Bloom 过滤器时,重要的是我们为预期的元素数量提供一个合理准确的值。否则,我们的过滤器将返回比预期高得多的误报率。让我们看一个例子。
假设我们创建了一个过滤器,其期望的误报概率为 1%,预期的一些元素等于 5,但随后我们插入了 100,000 个元素:
BloomFilter<Integer> filter = BloomFilter.create(
Funnels.integerFunnel(),
5,
0.01);
IntStream.range(0, 100_000).forEach(filter::put);
因为预期的元素数量很少,过滤器将占用很少的内存。
但是,随着我们添加的项目比预期的多,过滤器变得过饱和,并且返回误报结果的概率比期望的 1% 高得多:
assertThat(filter.mightContain(1)).isTrue();
assertThat(filter.mightContain(2)).isTrue();
assertThat(filter.mightContain(3)).isTrue();
assertThat(filter.mightContain(1_000_000)).isTrue();
请注意,即使对于我们之前没有插入过滤器的值,maycontatin()方法也会**返回true 。**