布隆过滤器
布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。(来自百度百科)。
布隆过滤器用来判断一个集合中的是否包含某一个元素,由于采用hash运算,有hash碰撞的原因,所以会存在误判。布隆过滤器判定一个元素存在的情况,这个元素可能不存在,但是判定一个元素不存在的时候,是一定不存在的。
布隆过滤器原理
- 布隆过滤器内部维护着一个bit数组。
- 布隆过滤器内部有很多个hash算法(散列算法)。
- 利用K个hash算法对key的值进行hash运算,并对hash值进行取数组长度模操作,最后的index值就是bit数组的索引下标,最后将index所在的bit位置为1.
index = hash1(key) % bit位长度
4.如果所有的hash值对应的下标都为1,则判定数据存在,只要有一个hash值对应的下标位不为1,则判定数据不存在。
为什么要用到K个hash算法?
关于hash算法
Hash,一般翻译做散列、杂凑,或音译为哈希,是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。(来自百度百科)
hash是一个压缩映射,也就是说输出会小于输入。一般这个过程是一段不可逆的过程。
不同的输入可能会造成相同的输出。
关于hash碰撞
不同的输入可能会造成相同的输出。
因为hash值是一段固定长度的输出。在二进制中,长度固定了,在对应存储空间中,大小也就相应确定了。不管是输出是字符串,还是整形。由于大小的确定,字符串也会有对应的长度,整形会有最大值。
为什么会有hash碰撞?
试想一下,世界上的字符串总共有多少呢???字符串肯定是无穷无尽的。
如果我们利用输出为整形的固定长度的输出来对应输入。如果固定长度的最大值为127(也就是一个字节),那么一个字节中的所有整形数量是多少?是不是255(-128 - 127)个(由于一个字节8位中,第一位是符号位)。也就是说固定长度一个字节的情况下,最多能支持255个不同的输入,一旦有不同的256个输入的时候,算出的hash值肯定会与之前的某一个相等。这就是hash碰撞。换一种说法就是不同的两个字符串在经过同一种hash算法后,得到了相同的值。
如果布隆过滤器用一个hash算法
当布隆过滤器只用一个hash算法时,如果遇到hash碰撞,则是两个不用的字符串得到了相同的hash值,两个字符串中,如果第一个字符串之前是加入到布隆过滤器,相应的下标值已经为1了,当第二个不同的字符串再次请求布隆过滤器过滤时,布隆过滤器由于看到了下标位对应的bit位是1,则会判定第二个字符串是存在的。这里实际上造成了误判。
如果布隆过滤器用两个hash算法
对照一个hash算法,当用两个hash算法时,第一个hash算法判定两个字符串都是相同的值,所以对应的bit位都置1,但是,第二个hash算法对于两个不相同的字符串,算出的hash值不一样。则他们第二个对应的bit位也不一样。这时,布隆过滤器是如何判定第二个字符串是否存在呢?
由于第二个字符串的第二个位置,
两种情况:
这里假设没有字符串对应所有的hash算法与第二个字符串的第二个hash算法的的值相同。则第二个字符串第二个hash算法对应的bit位是0,所以这时,布隆过滤器会判定第二个字符串是不存在的。这个与我们实际的期望相符。
如果假设有字符串对应的hash算法与第二个字符串中的第二个hash算法的值相同,则对应的bit位变成1,这时,布隆过滤器会判定第二个字符串是存在的,这里又造成了误判。所以这里是布隆过滤器判定存在时,则可能不存在的由来。
当我们提升了hash算法的个数,是否能有效避免上面的情况呢?
当我们将hahs算法提升到三个,则第二种情况下,还需要去判定第三个hash算法的值,这样减少了误判了几率。
所以提升hash算法的个数,可以有效的降低误判率。
误判率还跟什么有关系?
误判率实质是由于hash碰撞导致的,怎样降低hash碰撞的几率?
假设在一个6*6的棋盘里面,两个棋子落到相同位置(碰撞)的几率是多少?
如果在一个36*36棋盘里面呢?
如果在10000*10000的棋盘里面呢?
如果棋盘大小固定了,棋子从两个到几百个,是不是落到相同位置(碰撞)几率大大增加?
如果布隆过滤器的大小很大,而元素个数却很小,那是不是碰撞几率相应会小很多?
如果布隆过滤器的大小固定,而元素增加后,碰撞几率是不是增加了?
所以布隆过滤器的误判率跟布隆过滤器的大小和集合中的元素的多少有关系。
误判率的计算公式
m:Bloom Filter的大小 n:元素个数 k:哈希函数个数 p:误判率 p = (1 - e ^ (-nk/m)) ^ K
关于删除困难
我是真想不到这玩意儿要怎么删除?
试想一下,布隆过滤器删除一个元素的逻辑是将所对应的hash下标位对应的值置0,鬼知道你要置0的下标位有没有其他元素也落到这个位置上。
java写一个布隆过滤器
package cn.axj.redis; import java.util.BitSet; import java.util.UUID; /** * 布隆过滤器 * 需要构建几个hash函数,然后将元素进行hash,然后将hash值对应的位置设置为1 * * @author aoxiaojun * @date 2024/1/22 17:38 **/ public class BloomFilter { private BitSet container; private int size; public BloomFilter(int size){ this.size = size; this.container = new BitSet(size); } public void add(String value){ int firstHash = hash1(value); int secondHash = hash2(value); int thirdHash = hash3(value); container.set(firstHash % size, true); container.set(secondHash % size, true); container.set(thirdHash % size, true); } public boolean contains(String value){ int firstHash = hash1(value); if(!container.get(firstHash % size)){ return false; } int secondHash = hash2(value); if(!container.get(secondHash % size)){ return false; } int thirdHash = hash3(value); return container.get(thirdHash % size); } public int hash1(String key){ if(key == null || key.length() == 0){ throw new RuntimeException(); } int hash = 0; for (int i = 0; i < key.length(); i++) { hash += (key.charAt(i) * (i + 1)); } return hash; } public int hash2(String key){ if(key == null || key.length() == 0){ throw new RuntimeException(); } int hash = 0; for (int i = 0; i < key.length(); i++) { hash += (key.charAt(i) >> (i + 1)); } return hash; } public int hash3(String key){ if(key == null || key.length() == 0){ throw new RuntimeException(); } int hash = 0; for (int i = 0; i < key.length(); i++) { hash += (key.charAt(i) << (i + 1)); } return hash; } public static void main(String[] args) { BloomFilter bloomFilter = new BloomFilter(100000); String s = "hello world!"; String d = "hello"; String xx = "你好,世界!"; bloomFilter.add(s); bloomFilter.add(d); bloomFilter.add(xx); System.out.println(bloomFilter.contains(s)); System.out.println(bloomFilter.contains(xx)); System.out.println(bloomFilter.contains(d)); long start = System.currentTimeMillis(); for (int i = 0;i < 10;i++){ String randomString = UUID.randomUUID().toString(); System.out.println(bloomFilter.contains(randomString)); } System.out.println("时间花费" + (System.currentTimeMillis() - start)); } } //运行结果 true true true false false false false false false false false false false 时间花费369
redisson布隆过滤器的使用
public class RedissonBloomFilter { public static void main(String[] args) { Config config = new Config(); config.useSingleServer().setAddress("redis://192.168.32.128:6379"); // config.useSingleServer().setPassword(""); // 构造Redisson RedissonClient redissonClient = Redisson.create(config); // 初始化布隆过滤器:预计元素为100000000L个,误差率为3% RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter("phoneList"); //设置布隆过滤器的长度和误判率 bloomFilter.tryInit(100000000L, 0.03); // 将号码插入到布隆过滤器中 bloomFilter.add("10086"); // 判断下面的号码是否在布隆过滤器中 System.out.println(bloomFilter.contains("10000")); System.out.println(bloomFilter.contains("10086")); } }