布隆过滤器

1、什么是布隆过滤器

布隆过滤器(Bloom Filter),是1970年,由一个叫布隆的小伙子提出的,它实际上是一个很长的二进制向量和一系列随机映射函数。主要用于判断一个元素是否在一个集合中。

二进制大家应该都清楚,存储的数据不是0就是1,默认是0。主要用于判断一个元素是否在一个集合中,0代表不存在某个数据,1代表存在某个数据。

2、布隆过滤器原理

布隆过滤器由一个 bitSet 和 一组 Hash 函数(算法)组成,是一种空间效率极高的概率型算法和数据结构,主要用来判断一个元素是否在集合中存在。

2.1 存入过程

  • 将要添加的元素给 k 个哈希函数

  • 得到对应于位数组上的 k 个位置

  • 将这k个位置设为 1

例子:

在初始化时,bitSet 的每一位被初始化为0,同时会定义 Hash 函数,例如有3组 Hash 函数:hash1、hash2、hash3。

当我们要写入一个值时,过程如下,以“java”为例:

1)首先将“java”跟3组 Hash 函数分别计算,得到 bitSet 的下标为:1、7、10。

2)将 bitSet 的这3个下标标记为1。

3)按上面的流程跟 3组 Hash 函数分别计算,结果如下:

php:Hash 函数计算 bitSet 下标为:1、7、11

go:Hash 函数计算 bitSet 下标为:4、10、11

布隆过滤器_第1张图片

2.2 查询过程

  • 将要查询的元素给k个哈希函数

  • 得到对应于位数组上的k个位置

  • 如果k个位置有一个为 0,则肯定不在集合中

  • 如果k个位置全部为 1,则可能在集合中

结合2.1 的例子

当我们要查询一个值时,过程如下,同样以“java”为例::

1)首先将“java”跟3组 Hash 函数分别计算,得到 bitSet 的下标为:1、7、10。

2)查看 bitSet 的这3个下标是否都为1,如果这3个下标不都为1,则说明该值必然不存在,如果这3个下标都为1,则只能说明可能存在,并不能说明一定存在。

其实上图的例子已经说明了这个问题了,当我们只有值“java”和“php”时,bitSet 下标为1的有:1、4、7、10、11。

当我们又加入值“go”时,bitSet 下标为1的还是这5个,所以当 bitSet 下标为1的为:1、4、7、10、11 时,我们无法判断值“go”存不存在。

2.2.1 查询无法判断是否存在(误判)的原因

其根本原因是,不同的值在跟 Hash 函数计算后,可能会得到相同的下标,所以某个值的标记位,可能会被其他值给标上了。

这也是为啥布隆过滤器只能判断某个值可能存在,无法判断必然存在的原因。

但是反过来,如果该值根据 Hash 函数计算的标记位没有全部都为1,那么则说明必然不存在,这个是肯定的。

2.2.2 降低误判

降低这种误判率的思路也比较简单:

  1. 一个是加大 bitSet 的长度,这样不同的值出现“冲突”的概率就降低了,从而误判率也降低。

  1. 提升 Hash 函数的个数,Hash 函数越多,每个值对应的 bit 越多,从而误判率也降低。

3、优缺点

优点

  • 时间复杂度低,增加和查询元素的时间复杂为O(N),(N为哈希函数的个数,通常情况比较小)

  • 保密性强,布隆过滤器不存储元素本身

  • 存储空间小,如果允许存在一定的误判,布隆过滤器是非常节省空间的(相比其他数据结构如Set集合)

缺点

  • 有点一定的误判率,但是可以通过调整参数来降低

  • 无法获取元素本身

  • 很难删除元素

4、使用场景

  • 数据库防止穿库。 Google Bigtable,HBase 和 Cassandra 以及 Postgresql 使用BloomFilter来减少不存在的行或列的磁盘查找。避免代价高昂的磁盘查找会大大提高数据库查询操作的性能。

  • 业务场景中判断用户是否阅读过某视频或文章,比如抖音或头条,当然会导致一定的误判,但不会让用户看到重复的内容。

  • 缓存宕机、缓存击穿场景,一般判断用户是否在缓存中,如果在则直接返回结果,不在则查询db,如果来一波冷数据,会导致缓存大量击穿,造成雪崩效应,这时候可以用布隆过滤器当缓存的索引,只有在布隆过滤器中,才去查询缓存,如果没查询到,则穿透到db。如果不在布隆器中,则直接返回。

  • WEB拦截器,如果相同请求则拦截,防止重复被攻击。用户第一次请求,将请求参数放入布隆过滤器中,当第二次请求时,先判断请求参数是否被布隆过滤器命中。可以提高缓存命中率。Squid 网页代理缓存服务器在 cache digests 中就使用了布隆过滤器。Google Chrome浏览器使用了布隆过滤器加速安全浏览服务。

5、java示例

google的Guava工具类已经做好了封装,我们可以直接使用。

引入依赖


    com.google.guava
    guava
    ${guava.version}

测试

package com.ybw.test;

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

/**
 * 布隆过滤器测试
 *
 * @author ybw
 * @version V1.0
 * @className BloomFilterTest
 * @date 2023/1/10
 **/
@Slf4j
public class BloomFilterTest {

    /**
     * 预计要插入多少数据
     */
    private int size = 1000000;

    /**
     * 期望的误判率
     */
    private double fpp = 0.01;

    /**
     * 布隆过滤器
     */
    private BloomFilter bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, fpp);


    /**
     * 测试
     *
     * @methodName: filter
     * @return: void
     * @author: ybw
     * @date: 2023/1/10
     **/
    @Test
    void filter() {
        //1、插入10万样本数据
        for (int i = 0; i < size; i++) {
            bloomFilter.put(i);
        }

        //2、用另外十万测试数据,测试误判率
        //误判个数
        int count = 0;
        for (int i = size; i < size + 100000; i++) {
            if (bloomFilter.mightContain(i)) {
                count++;
                System.out.println(i + "误判了");
            }
        }
        log.info("总共的误判数:{}", count);
    }

}

测试结果

总共的误判数:947

误判率:0.000947<0.01

6、拓展

为了解决布隆过滤器不能删除元素的问题,布谷鸟过滤器横空出世。感兴趣的可以网上查相关信息。

你可能感兴趣的:(java,redis,哈希算法,算法)