上一篇我们使用缓存空值的java实现方式浅浅的解决了一个redis缓存穿透的解决方案,这一篇来看看布隆过滤器的实现。
布隆过滤器
简单来说,布隆过滤器他是实际上是一个很长的二进制向量和一系列的随机映射函数两部分组成的数据结构。我们不会将真实的数据值存储在布隆过滤器中,我们只会在布隆过滤器中存储一个key的映射对象,从而可以通过布隆过滤器来判断这个值是否存在,从而判断是否需要去进行查询。如果数据不在集合中,能被识别出来,不需要到数据库中进行查询,所以能将数据库查询返回值为空的查询过滤掉。
底层原理
当一个元素加入布隆过滤器中的时候,会进行如下操作:
使用布隆过滤器中的哈希函数对元素进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。
根据得到的哈希值,在位数组中把对应下标的值置为1。
当我们需要判断一个元素是否位于布隆过滤器的时候,会进行如下操作:
对给定元素再次进行相同的哈希计算;
得到值之后判断位数组中的每个元素是否都为1,如果值都为1,那么说明这个值在布隆过滤器中,如果存在一个值不为1,说明该元素不在布隆过滤器中。
当字符串存储要加入到布隆过滤器中时,该字符串首先由多个哈希函数生成不同的哈希值,然后将对应的位数组的下标设置为 1 (当位数组初始化时,所有位置均为 0)。当第二次存储相同字符串时,因为先前的对应位置已设置为 1,所以很容易知道此值已经存在(去重非常方便);
如果我们需要判断某个字符串是否在布隆过滤器中时,只需要对给定字符串再次进行相同的哈希计算,得到值之后判断位数组中的某个元素是否都为1,如果值都为1,那么说明这个值在布隆过滤器中,如果存在一个值不为1,说明该元素不在布隆过滤器中。
参考 Java实现布隆过滤器_java 布隆过滤器-CSDN博客
代码实现
从上面底层原理的介绍我们可以知道,布隆过滤器无非就是一个位数组,存放数据都是通过哈希函数计算出来数组位置,所以我们可以本地自己实现一个简易的布隆过滤器,大概步骤为
import java.util.BitSet;
public class MyBloomFilter {
//位数组大小
private static final int DEFAULT_SIZE = 2 << 24;
//通过这个数组创建多个Hash函数
private static final int[] SEEDS = new int[]{6, 18, 64, 89, 126, 189, 223};
//初始化位数组,数组中的元素只能是 0 或者 1
private BitSet bits = new BitSet(DEFAULT_SIZE);
//Hash函数数组
private MyHash[] myHashes = new MyHash[SEEDS.length];
//初始化多个包含 Hash 函数的类数组,每个类中的 Hash 函数都不一样
public MyBloomFilter() {
// 初始化多个不同的 Hash 函数
for (int i = 0; i < SEEDS.length; i++) {
myHashes[i] = new MyHash(DEFAULT_SIZE, SEEDS[i]);
}
}
//添加元素到位数组
public void add(Object value) {
for (MyHash myHash : myHashes) {
bits.set(myHash.hash(value), true);
}
}
//判断指定元素是否存在于位数组
public boolean contains(Object value) {
boolean result = true;
for (MyHash myHash : myHashes) {
result = result && bits.get(myHash.hash(value));
}
return result;
}
//自定义 Hash 函数
private class MyHash {
private int cap;
private int seed;
MyHash(int cap, int seed) {
this.cap = cap;
this.seed = seed;
}
//计算 Hash 值
int hash(Object obj) {
return (obj == null) ? 0 : Math.abs(seed * (cap - 1) & (obj.hashCode() ^ (obj.hashCode() >>> 16)));
}
}
}
同时谷歌公司提供了一个Guana的工具包,里面有一个布隆过滤器的完整实现,本地测试我们先使用谷歌大佬提供的工具进行测试。
java依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1.1-jre</version>
</dependency>
代码实现
public static void main(String[] args) {
// 初始化布隆过滤器,设计预计元素数量为100_0000L,误差率为1%
BloomFilter<CharSequence> bloomFilter = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8), 100_0000, 0.01);
int n = 100_0000;
for (int i = 0; i < n; i++) {
bloomFilter.put(String.valueOf(i));
}
int count = 0;
for (int i = 0; i < (n * 2); i++) {
if (bloomFilter.mightContain(String.valueOf(i))) {
count++;
}
}
System.out.println("过滤器误判率:" + 1.0 * (count - n) / n);
}