「什么是布隆过滤器」
本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure)
,特点是高效地插入和查询,可以用来告诉你 「“某样东西一定不存在或者可能存在”」。
相比于传统的 List
、Set
、Map
等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的。
「实现原理」
HashMap 的问题
讲述布隆过滤器的原理之前,我们先思考一下,通常你判断某个元素是否存在用的是什么?应该蛮多人回答 HashMap
吧,确实可以将值映射到 HashMap
的 Key
,然后可以在 O(1)
的时间复杂度内返回结果,效率奇高。但是 HashMap
的实现也有缺点,例如存储容量占比高,考虑到负载因子的存在,通常空间是不能被用满的,而一旦你的值很多例如上亿的时候,那 HashMap
占据的内存大小就变得很可观了。
还比如说你的数据集存储在远程服务器上,本地服务接受输入,而数据集非常大不可能一次性读进内存构建 HashMap
的时候,也会存在问题。
「布隆过滤器数据结构」
布隆过滤器是一个 bit
向量或者说 bit
数组,长这样:
http://static.cyblogs.com/QQ20200618-210117@2x.jpg
数组里面的值就只会存在true
与false
。
如果我们要映射一个值到布隆过滤器中,我们需要使用「多个不同的哈希函数」生成**多个哈希值,**并对每个生成的哈希值指向的 bit
位置 1
,例如针对值 “baidu
” 和三个不同的哈希函数分别生成了哈希值则上图转变为:
http://static.cyblogs.com/QQ20200618-210604@2x.jpg
而当我们需要查询 “baidu
” 这个值是否存在的话,那么哈希函数必然会返回图中的3
个bit
位,然后我们检查发现这三个 bit
位上的值均为 1
,那么我们可以说 “baidu
” 「存在了么?答案是不可以,只能是 “baidu” 这个值可能存在。」
这是为什么呢?答案跟简单,因为随着增加的值越来越多,被置为 1
的 bit
位也会越来越多,这样某个值 “taobao
” 即使没有被存储过,但是万一哈希函数返回的三个 bit
位都被其他值置为了 1
,那么程序还是会判断 “taobao
” 这个值存在。
支持删除么
传统的布隆过滤器并不支持删除操作。但是名为 Counting Bloom filter 的变种可以用来测试元素计数个数是否绝对小于某个阈值,它支持元素删除。可以参考文章 Counting Bloom Filter 的原理和实现:https://cloud.tencent.com/developer/article/1136056
Guava里的布隆过滤器:com.google.common.hash.BloomFilter
代码语言:javascript复制// 可能存在
public boolean mightContain(T object) {
return strategy.mightContain(object, funnel, numHashFunctions, bits);
}
// 放入值
public boolean put(T object) {
return strategy.put(object, funnel, numHashFunctions, bits);
}
如何选择哈希函数个数和布隆过滤器长度
很显然,过小的布隆过滤器很快所有的 bit
位均为 1
,那么查询任何值都会返回“可能存在”,起不到过滤的目的了。布隆过滤器的长度会直接影响误报率,布隆过滤器越长其误报率越小。
另外,哈希函数的个数也需要权衡,个数越多则布隆过滤器 bit
位置位 1
的速度越快,且布隆过滤器的效率越低;但是如果太少的话,那我们的误报率会变高。
http://static.cyblogs.com/QQ20200618-212220@2x.jpg
我们可以参考网站给的一个参考值:https://krisives.github.io/bloom-calculator/
http://static.cyblogs.com/QQ20200618-212520@2x.jpg
比如:我们有1000
的数量,误判率是0.1
,那么需要3.32
个hash
函数,位的长度为4793
。
Redis-BloomFilter实践
Redis
在4.0
版本推出了 module
的形式,可以将 module
作为插件额外实现Redis
的一些功能。官网推荐了一个 RedisBloom
作为 Redis
布隆过滤器的 Module
。
RedisBloom
需要先进行安装,推荐使用Docker
进行安装,简单方便:
docker pull redislabs/rebloom:latest
docker run -p 6379:6379 --name redis-redisbloom redislabs/rebloom:latest
docker exec -it redis-redisbloom bash
# redis-cli
# 127.0.0.1:6379> bf.add tiancheng hello
熟悉一下布隆过滤器基本指令:
bf.add
添加元素到布隆过滤器bf.exists
判断元素是否在布隆过滤器bf.madd
添加多个元素到布隆过滤器,bf.add
只能添加一个bf.mexists
判断多个元素是否在布隆过滤器
127.0.0.1:6379> bf.add tiancheng tc01
(integer) 1
127.0.0.1:6379> bf.add tiancheng tc02
(integer) 1
127.0.0.1:6379> bf.add tiancheng tc03
(integer) 1
127.0.0.1:6379> bf.exists tiancheng tc01
(integer) 1
127.0.0.1:6379> bf.exists tiancheng tc02
(integer) 1
127.0.0.1:6379> bf.exists tiancheng tc03
(integer) 1
127.0.0.1:6379> bf.exists tiancheng tc04
(integer) 0
127.0.0.1:6379> bf.madd tiancheng tc05 tc06 tc07
1) (integer) 1
2) (integer) 1
3) (integer) 1
127.0.0.1:6379> bf.mexists tiancheng tc05 tc06 tc07 tc08
1) (integer) 1
2) (integer) 1
3) (integer) 1
4) (integer) 0
Redis Bitmap实现简单的布隆过滤器
Bitmap
在Redis
中并不是一个单独的数据类型,而是由字符串类型(Redis
内部称Simple Dynamic String,SDS
)之上定义的与比特相关的操作实现的,此时SDS
就被当做位数组了。下面是在redis-cli
中使用getbit
和setbit
指令的操作示例。
# 字符串"meow"的二进制表示:01101101 01100101 01101111 01110111
es1:19000> set bitmap_cat "meow"
OK
# 最低位下标为0。取得第3位的比特(0)
es1:19000> getbit bitmap_cat 3
(integer) 0
# 取得第23位的比特(1)
es1:19000> getbit bitmap_cat 23
(integer) 1
# 将第7位设为0
es1:19000> setbit bitmap_cat 7 0
(integer) 1
# 将第14位设为1
es1:19000> setbit bitmap_cat 14 1
(integer) 0
# 修改过后的字符串变成了"lgow"
es1:19000> get bitmap_cat
"lgow"
Redis
的Bitmap
是自动扩容的,亦即get/set
到高位时,就会主动填充0
。此外,还有bitcount
指令用于计算特定字节范围内1
的个数,bitop
指令用来执行位运算(支持and
、or
、xor
和not
)。相应的用法可以查询Redis
官方文档等。
参考地址
- https://www.jasondavies.com/bloomfilter/
- https://zhuanlan.zhihu.com/p/43263751
- https://www.jianshu.com/p/c2defe549b40
如果大家喜欢我的文章,可以关注个人订阅号。欢迎随时留言、交流。如果想加入微信群的话一起讨论的话,请加管理员简栈文化-小助手(lastpass4u),他会拉你们进群。