布隆过滤器
什么是布隆过滤器
- 布隆过滤器(Bloom Filter)是有布隆在 1970 年提出的。它实际上是由一个很长的二进制向量和一系列随机映射函数组成。布隆过滤器是用来检索一个元素是否在一个集合中。
* 优点:空间效率和查询时间都远远超过一般算法,因为使用数组存入,同时使用二进制,占用空间小
* 缺点:
1. 有一定的误识别率
2. 很难做删除操作
原理
对 Key 进行 n 个 hash 算法获取 n 个值,在比特数组中将这 k 个值三列后设定为1。查的时候如果特定的这几个位置都为1,那么布隆过滤器判断该 Key 存在
增删查
- 增:经过多个 hash 函数,把
- 删:
- 查
说明
布隆过滤器是一种数据结构,一种概率性的数据结果,它会告诉你“某样东西一定不存在或者可能存在”
布隆过滤器是一个 bit 向量或者说 bit 数组,如下:
现在,我们把 “AliPay” 给存储进去,存储过程:将要映射的值,使用多个不同的 hash 函数生产多个哈希值,然后每个生产的哈希值执行的 bit 置为1
同样,现在我要存储另外一个值 “WechatPay” ,那么可能映射之后就是下面这样
4 号位置的值,刚开始给 “AliPay” 了,后来 “WechatPay” 也在那里,这样的话,值不就给覆盖掉了嘛
嗯,没错,是被覆盖掉了
接下来,我们查询 “Ali” 那么查询之后,布隆过滤器可能会给你 “0,1,2” 的值, 结果呢 “2” 的位置是 0 ,说明没有任何值映射到这个位置上来,所以我们就可以判定数据库里面没有 “Ali” 这个值
那查询 “AliPay” 的话,毫无疑问,肯定会返回给我 “1,4,6” ,那我们能说数据库里面一定有 “AliPay” 么?不能,因为 “1,4,6” 的值有可能被其他的值给覆盖到了,所以我们只能说,数据库里可能存在 “AliPay”
这就是布隆过滤器说的"某个值一定不存在或者可能存在"
布隆过滤器使用场景
- 针对缓存穿透,使用布隆过滤器,防止不存在的数据直接打到数据库上
- 去重:比如新闻推荐,当推荐系统推荐新闻时会从每个用户的历史记录里进行筛选,过滤掉已经存在的记录。如果使用 Mysql 类似的关系型数据库,需要进行大量的查询,并发量很大时,数据库压力很大。在用户浏览记录存入数据库时,把记录也保存一份到布隆过滤器,推送新闻的时候,通过布隆过滤器判断,推送内容存在就不推送了,不存在就推送。
实际开发使用布隆过滤器
直接采用 Guava 的 BloomFiler
布隆过滤器实现
- MyBloomFilter.java
import java.util.Arrays;
import java.util.BitSet;
/**
* 布隆过滤器
*/
public class MyBloomFilter {
//布隆过滤器容量
private static final int DEFAULT_SIZE = 2 << 28;
// bit 数组,用来存放结果
private static BitSet bitSet = new BitSet(DEFAULT_SIZE);
// hash 函数会用到,用来生成不同的 hash 值,可随意设置
private static final int[] ints = {1, 6, 16, 38, 58, 68};
// hash 函数,借鉴了 hashmap 的扰动算法
private int hash(Object key, int i) {
int h;
return key == null ? 0 : (i * (DEFAULT_SIZE - 1) & ((h = key.hashCode()) ^ (h >>> 16)));
}
// add 方法,计算出 key 的 hash 值,并将对应下标置为 true
public void add(Object key) {
Arrays.stream(ints).forEach(i -> bitSet.set(hash(key, i)));
}
// 判断 key 是否存在,true 不一定说明 key 存在,但是 false 一定说明不存在
public boolean isContain(Object key) {
boolean result = true;
for (int i : ints) {
// 短路与,只要有一个 bit 位为false,则返回false
result = result && bitSet.get(hash(key, i));
}
return result;
}
}
- 测试数据
public class MyBloomFilterTest {
public static void main(String[] args) {
MyBloomFilter bloomFilter = new MyBloomFilter();
bloomFilter.add("张学友");
bloomFilter.add("郭德纲");
bloomFilter.add("刘德华");
System.out.println(bloomFilter.isContain("张学友"));
System.out.println(bloomFilter.isContain("郭德纲"));
System.out.println(bloomFilter.isContain("刘德华"));
System.out.println(bloomFilter.isContain("666"));
System.out.println(bloomFilter.isContain("888"));
}
}
测试结果:
true
true
true
false
false
参考:
https://blog.csdn.net/qq_33709582/article/details/108407706
https://mp.weixin.qq.com/s/SlfLgsfbvytxNS46fTFUdA