1 什么是布隆过滤器
布隆过滤器(Bloom Filter): 1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列的随机映射函数(哈希函数)两部分组成的数据结构。
用途: 用于检索一个元素是否在一个集合中。
——你可以永远相信布隆
2 布隆过滤器的原理
布隆过滤器的数据结构是一个位向量,也就是一个由0、1所组成的bit数组
2.1 数据结构
2.2 添加元素
每个元素添加进布隆过滤器前,都会经过多个不同的哈希函数,计算出不同的哈希值,然后映射到位向量上,也就是对应的位"置1"
2.3 判断元素
对元素进行多个不同的哈希运算,得到多个位下标,判断所有映射位置是否都为1,若是,则元素可能存在,否则一定不存在
注意:由于不同的值通过哈希函数之后可能会映射到相同的位置,因此如果一个不存在的元素对应的位都被其他元素所设置1,则查询时就会误判
2.4 删除元素
布隆过滤器对元素的删除不太支持,因为元素删除不能简单的把对应的若干位设置为0,会对其他元素有影响。
目前有一些变形的特定布隆过滤器支持元素的删除(尽可能不考虑删除)。
3 布隆过滤器的优缺点
优点:
时间复杂度低,增加及查询元素的时间复杂度都是O(k),k为Hash函数的个数;
保密性强,布隆过滤器不存储元素本身;
占用存储空间小,布隆过滤器相对于其他数据结构(如Set、Map)非常节省空间;
缺点:
存在误判,只能证明一个元素一定不存在或者可能存在,返回结果是概率性的,但是可以通过调整参数来降低误判比例;
删除困难,一个元素映射到bit数组上的k个位置为1,删除的时候不能简单的直接置为0,可能会影响到其他元素的判断;
无法通过key找到元素本身;
3 布隆过滤器适用场景
- 解决Redis缓存穿透问题(访问非法key)
- 黑名单过滤
- 爬虫任务处理过滤,爬过的不再爬
- 解决新闻推荐过的不再推荐(类似抖音刷过的往下滑动不再刷到)
- HBase\RocksDB\LevelDB等数据库内置布隆过滤器
4 代码示例
4.1 Java示例
// 初始化布隆过滤器,设计预计元素数量为100w,误差率为1‰
BloomFilter<Long> bloomFilter = BloomFilter.create(Funnels.longFunnel(), 1000000, 0.001);
int n = 1000000;
for (long i = 0; i < n; i++) {
bloomFilter.put(i);
}
int containCount = (n * 5);//匹配总次数
int rightCount = 0;//正确次数
int errCount = 0;//误判次数
for (long i = 0; i < containCount; i++) {
if (bloomFilter.mightContain(i)) {
if (i < n) {
rightCount++;
} else {
errCount++;
}
} else {
if (i >= n) {
rightCount++;
} else {
errCount++;
}
}
}
long size = ObjectSizeCalculator.getObjectSize(bloomFilter);
System.out.println(String.format("匹配次数:%s,正确次数:%s,错误次数:%s", containCount, rightCount, errCount));
System.out.println("过滤器误判率:" + NumberUtil.roundStr(errCount * 1.0 / containCount, 10));
System.out.println("占用空间,bloomSize:" + NumberUtil.roundStr((size * 1.0 / 1024 / 1024), 4) + "MB");
4.2 Redis示例
@Resource
private RedissonClient redissonClient;
/**
* 容量
*/
private static Long expectedInsertions = 1000000L;
/**
* 误差
*/
private static Double falseProbability = 0.001;
private void add(String key, String data) {
RBloomFilter bloomFilter = redissonClient.getBloomFilter(key);
bloomFilter.tryInit(expectedInsertions, falseProbability);
bloomFilter.add(data);
}
private Boolean exist(String key, String data) {
RBloomFilter bloomFilter = redissonClient.getBloomFilter(key);
bloomFilter.tryInit(expectedInsertions, falseProbability);
boolean isExist = bloomFilter.contains(data);
return isExist;
}
//测试
{
add("bloom:place", "abc");
Boolean check1 = exist("bloom:place", "abc");
log.info("check1:{}", check1);
Boolean check2 = exist("bloom:place", "abcd");
log.info("check2:{}", check2);
return assembleResponse("ok,");
}