【数据结构与算法】布隆(Bloom Filter)过滤器

news2024/10/7 20:25:08

文章目录

  • 1、什么是布隆过滤器
  • 2、布隆过滤器的使用场景
  • 3、布隆过滤器的原理
    • 3.1 数据结构
    • 3.2 空间计算
    • 3.3 增加元素
    • 3.4 查询元素
    • 3.5 修改元素
    • 3.6 删除元素
  • 4、Redis集成布隆过滤器
    • 4.1 版本要求
    • 4.2 安装&编译
      • 4.2.1 下载插件压缩包
      • 4.2.2 解压
      • 4.2.3 编译插件
    • 4.3 Redis集成
      • 4.3.1 Redis配置文件修改
      • 4.3.2 测试是否成功
  • 5、Redis中布隆过滤器指令使用
    • 5.1 bf.add
    • 5.2 bf.madd
    • 5.3 bf.exists
    • 5.4 bf.mexists
  • 6、Java本地内存使用布隆过滤器
    • 6.1 引入pom依赖
    • 6.2 编写测试代码
    • 6.3 测试结果
    • 6.4 参数说明
    • 6.5 fpp&expectedInsertions
  • 7、Java集成Redis使用布隆过滤器
    • 7.1 引入pom依赖
    • 7.2 编写测试代码
    • 7.3 测试结果

1、什么是布隆过滤器

布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

上面这句介绍比较全面的描述了什么是布隆过滤器,如果还是不太好理解的话,就可以把布隆过滤器理解为一个set集合,我们可以通过add往里面添加元素,通过contains来判断是否包含某个元素。由于本文讲述布隆过滤器时会结合Redis来讲解,因此类比为Redis中的Set数据结构会比较好理解,而且Redis中的布隆过滤器使用的指令与Set集合非常类似(后续会讲到)。

学习布隆过滤器之前有必要先聊下它的优缺点,因为好的东西我们才想要嘛!

  • 布隆过滤器的优点:

    • 时间复杂度低,增加和查询元素的时间复杂为O(N),(N为哈希函数的个数,通常情况比较小)
    • 保密性强,布隆过滤器不存储元素本身
    • 存储空间小,如果允许存在一定的误判,布隆过滤器是非常节省空间的(相比其他数据结构如Set集合)
  • 布隆过滤器的缺点:

    • 有点一定的误判率,但是可以通过调整参数来降低
    • 无法获取元素本身
    • 很难删除元素

2、布隆过滤器的使用场景

布隆过滤器可以告诉我们 “某样东西一定不存在或者可能存在”,也就是说布隆过滤器说这个数不存在则一定不存,布隆过滤器说这个数存在可能不存在(误判,后续会讲),利用这个判断是否存在的特点可以做很多有趣的事情。

  • 解决Redis缓存穿透问题(面试重点)
  • 邮件过滤,使用布隆过滤器来做邮件黑名单过滤
  • 对爬虫网址进行过滤,爬过的不再爬
  • 解决新闻推荐过的不再推荐(类似抖音刷过的往下滑动不再刷到)
  • HBase\RocksDB\LevelDB等数据库内置布隆过滤器,用于判断数据是否存在,可以减少数据库的IO请求

3、布隆过滤器的原理

3.1 数据结构

布隆过滤器它实际上是一个很长的二进制向量和一系列随机映射函数。以Redis中的布隆过滤器实现为例,Redis中的布隆过滤器底层是一个大型位数组(二进制数组)+多个无偏hash函数
一个大型位数组(二进制数组):

在这里插入图片描述

多个无偏hash函数:
无偏hash函数就是能把元素的hash值计算的比较均匀的hash函数,能使得计算后的元素下标比较均匀的映射到位数组中。

如下就是一个简单的布隆过滤器示意图,其中k1、k2代表增加的元素,a、b、c即为无偏hash函数,最下层则为二进制数组。

在这里插入图片描述

3.2 空间计算

在布隆过滤器增加元素之前,首先需要初始化布隆过滤器的空间,也就是上面说的二进制数组,除此之外还需要计算无偏hash函数的个数。布隆过滤器提供了两个参数,分别是预计加入元素的大小n,运行的错误率f。布隆过滤器中有算法根据这两个参数会计算出二进制数组的大小l,以及无偏hash函数的个数k。
它们之间的关系比较简单:

  • 错误率越低,位数组越长,控件占用较大
  • 错误率越低,无偏hash函数越多,计算耗时较长

如下地址是一个免费的在线布隆过滤器在线计算的网址:

https://krisives.github.io/bloom-calculator/

在这里插入图片描述

3.3 增加元素

往布隆过滤器增加元素,添加的key需要根据k个无偏hash函数计算得到多个hash值,然后对数组长度进行取模得到数组下标的位置,然后将对应数组下标的位置的值置为1

  • 通过k个无偏hash函数计算得到k个hash值
  • 依次取模数组长度,得到数组索引
  • 将计算得到的数组索引下标位置数据修改为1

例如,key = Liziba,无偏hash函数的个数k=3,分别为hash1、hash2、hash3。三个hash函数计算后得到三个数组下标值,并将其值修改为1.
如图所示:

在这里插入图片描述

3.4 查询元素

布隆过滤器最大的用处就在于判断某样东西一定不存在或者可能存在,而这个就是查询元素的结果。其查询元素的过程如下:

  • 通过k个无偏hash函数计算得到k个hash值
  • 依次取模数组长度,得到数组索引
  • 判断索引处的值是否全部为1,如果全部为1则存在(这种存在可能是误判),如果存在一个0则必定不存在

关于误判,其实非常好理解,hash函数在怎么好,也无法完全避免hash冲突,也就是说可能会存在多个元素计算的hash值是相同的,那么它们取模数组长度后的到的数组索引也是相同的,这就是误判的原因。例如李子捌和李子柒的hash值取模后得到的数组索引都是1,但其实这里只有李子捌,如果此时判断李子柒在不在这里,误判就出现啦!因此布隆过滤器最大的缺点误判只要知道其判断元素是否存在的原理就很容易明白了!

3.5 修改元素


3.6 删除元素

布隆过滤器对元素的删除不太支持,目前有一些变形的特定布隆过滤器支持元素的删除!关于为什么对删除不太支持,其实也非常好理解,hash冲突必然存在,删除肯定是很苦难的!

4、Redis集成布隆过滤器

4.1 版本要求

  • 推荐版本6.x,最低4.x版本,可以通过如下命令查看版本:
redis-server -v

在这里插入图片描述

  • 插件安装,网上大部分推荐v1.1.1,文章写的时候v2.2.6已经是release版本了,用户自己选择,地址全在下面(2.2.6官网介绍说是1.0版本的维护版本,如果不想使用新的功能,无需升级!)

在这里插入图片描述

v1.1.1

https://github.com/RedisLabsModules/rebloom/archive/v1.1.1.tar.gz

v2.2.6

https://github.com/RedisLabsModules/rebloom/archive/v2.2.6.tar.gz

4.2 安装&编译

以下安装全部在指定目录下完成,可以选择一个合适的统一目录进行软件安装和管理。

4.2.1 下载插件压缩包

wget https://github.com/RedisLabsModules/rebloom/archive/v2.2.6.tar.gz

4.2.2 解压

tar -zxvf v2.2.6.tar.gz

4.2.3 编译插件

cd RedisBloom-2.2.6/
make

在这里插入图片描述

编译成功后看到redisbloom.so文件即可

4.3 Redis集成

4.3.1 Redis配置文件修改

  • 在redis.conf配置文件中加入如RedisBloom的redisbloom.so文件的地址
  • 如果是集群则每个配置文件中都需要加入redisbloom.so文件的地址
  • 添加完成后需要重启redis
loadmodule /usr/local/soft/RedisBloom-2.2.6/redisbloom.so

redis.conf配置文件中预置了loadmodule的配置项,我们可以直接在这里修改,后续修改会更加方便。
在这里插入图片描述

  • 保存退出后一定要记得重启Redis!

4.3.2 测试是否成功

Redis集成布隆过滤器的主要指令如下:

  • bf.add 添加一个元素
  • bf.exists 判断一个元素是否存在
  • bf.madd 添加多个元素
  • bf.mexists 判断多个元素是否存在

连接客户端进行测试,如果指令有效则证明集成成功

在这里插入图片描述

如果出现如下情况(error) ERR unknown command ,可以通过如下方法检查:

  • SHUTDOWN Redis实例,再重启实例,再次测试
  • 检查配置文件是否配置redisbloom.so文件地址正确
  • 检查Redis的版本是否过低

在这里插入图片描述

5、Redis中布隆过滤器指令使用

5.1 bf.add

bf.add表示添加单个元素,添加成功返回1

127.0.0.1:6379> bf.add name liziba
(integer) 1

在这里插入图片描述

5.2 bf.madd

bf.madd表示添加多个元素

127.0.0.1:6379> bf.madd name liziqi lizijiu lizishi
1) (integer) 1
2) (integer) 1
3) (integer) 1

5.3 bf.exists

bf.exists表示判断元素是否存在,存在则返回1,不存在返回0

127.0.0.1:6379> bf.mexists name liziba
1) (integer) 1

在这里插入图片描述

5.4 bf.mexists

bf.mexists表示判断多个元素是否存在,存在的返回1,不存在的返回0

127.0.0.1:6379> bf.mexists name liziqi lizijiu liziliu
1) (integer) 1
2) (integer) 1
3) (integer) 0

在这里插入图片描述

6、Java本地内存使用布隆过滤器

使用布隆过滤器的方式有很多,还有很多大佬自己手写的,我这里使用的是谷歌guava包中实现的布隆过滤器,这种方式的布隆过滤器是在本地内存中实现。

6.1 引入pom依赖

<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>29.0-jre</version>
</dependency>

6.2 编写测试代码

package com.lizba.bf;
 
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
 
/**
 * <p>
 *        布隆过滤器测试代码
 * </p>
 *
 */
public class BloomFilterTest {
 
    /** 预计插入的数据 */
    private static Integer expectedInsertions = 10000000;
    /** 误判率 */
    private static Double fpp = 0.01;
    /** 布隆过滤器 */
    private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), expectedInsertions, fpp);
 
    public static void main(String[] args) {
        // 插入 1千万数据
        for (int i = 0; i < expectedInsertions; i++) {
            bloomFilter.put(i);
        }
 
        // 用1千万数据测试误判率
        int count = 0;
        for (int i = expectedInsertions; i < expectedInsertions *2; i++) {
            if (bloomFilter.mightContain(i)) {
                count++;
            }
        }
        System.out.println("一共误判了:" + count);
 
    }
 
}

6.3 测试结果

误判了100075次,大概是expectedInsertions(1千万)的0.01,这与我们设置的 fpp = 0.01非常接近。

在这里插入图片描述

6.4 参数说明

在guava包中的BloomFilter源码中,构造一个BloomFilter对象有四个参数:

  • Funnel funnel:数据类型,由Funnels类指定即可
  • long expectedInsertions:预期插入的值的数量
  • fpp:错误率
  • BloomFilter.Strategy:hash算法

6.5 fpp&expectedInsertions

  • 当expectedInsertions=10000000&&fpp=0.01时,位数组的大小numBits=95850583,hash函数的个数numHashFunctions=7

在这里插入图片描述

  • 当expectedInsertions=10000000&&fpp=0.03时,位数组的大小numBits=72984408,hash函数的个数numHashFunctions=5
    在这里插入图片描述

  • 当expectedInsertions=100000&&fpp=0.03时,位数组的大小numBits=729844,hash函数的个数numHashFunctions=5
    在这里插入图片描述

综上三次测试可以得出如下结论:

  • 当预计插入的值的数量不变时,偏差值fpp越小,位数组越大,hash函数的个数越多
  • 当偏差值不变时,预计插入的中的数量越大,位数组越大,hash函数并没有变化(注意这个结论只是在guava实现的布隆过滤器中的算法符合,并不是说所有的算法都是这个结论,我做了多次测试,确实numHashFunctions在fpp相同时,是不变的!)

7、Java集成Redis使用布隆过滤器

Redis经常会被问道缓存击穿问题,比较优秀的解决办法是使用布隆过滤器,也有使用空对象解决的,但是最好的办法肯定是布隆过滤器,我们可以通过布隆过滤器来判断元素是否存在,避免缓存和数据库都不存在的数据进行查询访问!在如下的代码中只要通过bloomFilter.contains(xxx)即可,我这里演示的还是误判率!

7.1 引入pom依赖

<dependency>
  <groupId>org.redisson</groupId>
  <artifactId>redisson-spring-boot-starter</artifactId>
  <version>3.16.0</version>
</dependency>

7.2 编写测试代码

package com.lizba.bf;
 
import org.redisson.Redisson;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
 
/**
 * <p>
 *      Java集成Redis使用布隆过滤器防止缓存穿透方案
 * </p>
 *
 */
public class RedisBloomFilterTest {
 
    /** 预计插入的数据 */
    private static Integer expectedInsertions = 10000;
    /** 误判率 */
    private static Double fpp = 0.01;
 
    public static void main(String[] args) {
        // Redis连接配置,无密码
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.211.108:6379");
        // config.useSingleServer().setPassword("123456");
 
        // 初始化布隆过滤器
        RedissonClient client = Redisson.create(config);
        RBloomFilter<Object> bloomFilter = client.getBloomFilter("user");
        bloomFilter.tryInit(expectedInsertions, fpp);
 
        // 布隆过滤器增加元素
        for (Integer i = 0; i < expectedInsertions; i++) {
            bloomFilter.add(i);
        }
 
        // 统计元素
        int count = 0;
        for (int i = expectedInsertions; i < expectedInsertions*2; i++) {
            if (bloomFilter.contains(i)) {
                count++;
            }
        }
        System.out.println("误判次数" + count);
 
    }
 
}

7.3 测试结果

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/529407.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《硅谷钢铁侠:埃隆·马斯克的冒险人生》成就21世纪的史诗

《硅谷钢铁侠&#xff1a;埃隆马斯克的冒险人生》成就21世纪的史诗 阿什利万斯&#xff08;Ashlee Vance&#xff09;&#xff1a;美国商业专栏作家、资深科技记者。 文章目录 《硅谷钢铁侠&#xff1a;埃隆马斯克的冒险人生》成就21世纪的史诗马斯克的超级公司摘录感悟梦 马斯…

免费SSL:阿里云SSL证书免费申请入口及流程开启HTTPS

阿里云SSL免费证书在哪申请&#xff1f;一个阿里云账号一年可以申请20张免费SSL证书&#xff0c;很多同学找不到免费SSL的入口&#xff0c;阿小云来详细说下阿里云SSL证书免费申请入口链接以及免费SSL证书申请流程&#xff0c;有同学反馈阿里云免费SSL证书没有了&#xff1f;错…

【Python共享文件】——Python快速搭建HTTP web服务实现文件共享并公网远程访问

文章目录 1. 前言2. 视频教程3. 本地文件服务器搭建3.1 python的安装和设置3.2 cpolar的安装和注册 4. 本地文件服务器的发布4.1 Cpolar云端设置4.2 Cpolar本地设置 5. 公网访问测试6. 结语 1. 前言 数据共享作为和连接作为互联网的基础应用&#xff0c;不仅在商业和办公场景有…

[离散数学]图论

图基本概念 点相同 边相同 $$ 有向图 无向图 邻接点 &#xff1a;两个结点有一条有(无)向边相关联 邻接边:关联与同一个结点 孤立结点: 不予任何结点相邻接的结点 握手定理 度数边的两倍 有向图的 出度和入度和边数 n个节点无向完全图边数 C n 2 1 2 n ( n − 1 ) C_n^2…

14JS05——流程控制-分支

目标&#xff1a; 1、流程控制 2、顺序流程控制 3、分支流程控制if语句 4、三元表达式 5、分支流程控制switch语句 一、流程控制 在一个程序执行的过程中&#xff0c;各条代码的执行顺序对程序的结果是有直接影响的。很多时候我们要通过控制代码 的执行顺序来实现我们要完成的…

Science | 人体可以依靠饥饿感来延缓衰老

作为一种高级动物&#xff0c;人体需要六种营养物质来维持基本的生理需求&#xff1a;糖类、油脂、蛋白质、无机盐、水、和维生素。对营养物质的生理需求促使人和动物去追寻食物。而饮食对人和动物的行为和寿命又有着显著影响。 近年来&#xff0c;越来越多的研究表明&#xf…

计算机网络 二 (物理层)

物理层 概念 物理层为数据链路层屏蔽了各种传输媒体的差异&#xff0c;使数据链路层只需要考虑如何完成本层的协议和服务&#xff0c;而不必考虑网络具体的传输媒体是什么。 对于物理层有很多很多的协议&#xff0c;不过都不怎么重要&#xff0c;对于物理层我们知道物理层协议…

国产仪器仪表 1466C-V/1466D-V/1466E-V/1466G-V/1466H-V/1466L-V系列信号发生器

国产Ceyear 1466-V系列信号发生器是一款面向微波毫米波尖端测试的通用测试仪器&#xff0c;频率范围覆盖宽、射频调制带宽大、信号频谱纯度高&#xff0c;具有高准确度和大动态范围的功率输出&#xff0c;以及出色的矢量调制精度和ACPR性能&#xff0c;搭配单机双射频通道和多机…

21天学会Linux----Day1:Linux环境搭建

CSDN的uu们&#xff0c;大家好。这里是Linux的第一讲。 座右铭&#xff1a;前路坎坷&#xff0c;披荆斩棘&#xff0c;扶摇直上。 博客主页&#xff1a; 姬如祎 收录专栏&#xff1a;Linux保姆级教程 目录 1. Linux环境搭建的三种方式 2. 阿里云学生认证白嫖七个月云服务器…

十四、Zuul网关

目录 一、API网关作用&#xff1a; 二、网关主要功能&#xff1a; 2.1、统一服务入口 2.2、接口鉴权 2.3、智能路由 2.4、API接口进行统一管理 2.5、限流保护 三、 新建一个项目作为网关服务器 3.1、项目中引入Zuul网关依赖 3.2、在项目application.yml中配置网关路由…

mmdetection 中 Mask Rcnn检测结果可视化(DICE计算、PR曲线绘制等)

mmdetection中的Mask Rcnn是一个很不错的检测网络&#xff0c;既可以实现目标检测&#xff0c;也可以实现语义分割。官方也有很详细的doc指导&#xff0c;但是对新手来说并不友好&#xff0c;刚好之前笔者写的mmlab系列里面关于可视化都还没有一个详细的文档&#xff0c;也在此…

JAVA常用API - Runtime和System

文章目录 前言 大家好,我是最爱吃兽奶,今天给大家带来JAVA常用API中的Runtime类和System类 那么就让我们一起去看看吧! 一、Rubtime 1.Rubtime是什么? 2.Runtime常用方法 Runtime提供了很多方法,在这里演示两个 public static Runtime getRuntime(): 返回当前运行时环境的…

ChatSQL - 文本生成SQL【LLM】

ChatSQL将用户提供的纯文本转换为 mysql 查询&#xff0c;基于ChatGPT实现。 推荐&#xff1a;用 NSDT设计器 快速搭建可编程3D场景。 1、ChatSQL简介 我们需要从一开始就指定一些关于我们数据库的信息&#xff0c;以便 Chatgpt 了解我们的数据库。 info.json 文件可用于此过程…

什么是胆汁酸,其与肠道微生物互作如何影响人体健康

谷禾健康 胆汁酸是一种代谢胆固醇的物质&#xff0c;它具有两个亲和性不同的区域&#xff0c;即一部分能够与水分子相互作用&#xff08;亲水&#xff09;&#xff0c;而另一部分则不能与水分子相互作用&#xff08;疏水&#xff09;。 由于拥有这种两亲性质&#xff0c;胆汁酸…

开悟和悟道的一点看法

今天看到一个老师的在讲何为开悟&#xff0c;就做了一下笔记&#xff1a; 开悟就是明心见性&#xff0c;去除了见惑&#xff0c;见解方面&#xff0c;思想方面的迷惑&#xff0c;包含&#xff1a;身见&#xff08;我和身体关系&#xff0c;我是谁&#xff09;&#xff0c;边见…

Vue3-黑马(十二)

目录&#xff1a; &#xff08;1&#xff09;vue3-router-图标的二次封装-jxl组件 &#xff08;2&#xff09;vue3-进阶-动态路由 &#xff08;3&#xff09;vue3-进阶-动态路由2 &#xff08;1&#xff09;vue3-router-图标的二次封装-jxl组件 以.vue结尾结尾的单文件组件…

谈谈OPCUA 聚合服务器(aggregation-server)

OPC UA 作为一种以信息模型为基础的工业自动化通信协议&#xff0c;如同它的名字一样正在朝着“统一架构”的方向飞速地发展&#xff0c;从传感器&#xff0c;PLC &#xff0c;传感器到云端应用都正在向OPCUA 迁移。它将成为开放型系统&#xff0c;数字孪生&#xff0c;仿真系统…

Ubuntu20.04 -- 小白系列4 之小笔记

1、clash for linux Releases Dreamacro/clash GitHub 下载&#xff0c;建议这圈中的第一个。 解压后可执行文件改为clash 打开终端&#xff08;打开你的suying,并复制自己的&#xff09; wget -O config.yml https://s&log-levelinfo config.yml 复制并修改文件名为…

百度地图JavaScript API添加自定义Marker

百度地图JavaScript API添加自定义Marker 官网指导添加自定义Marker 实际使用中发现无法显示图标&#xff0c;找了一些博客 百度地图开发自定义图标无法显示的问题 百度地图自定义图标不显示问题解决方案 关于百度地图开放平台api覆盖物“自定义Marker图标”不能正常显示的解决…

bgp路由策略

* - valid 有效的, > - best 最佳的 上图中&#xff0c;有*和>&#xff0c;是有效最佳的。而没有*和没有>&#xff0c;是无效的&#xff0c;下一跳不可达 1--64511是公有AS 64512-65534为私有AS //属于哪个大的联盟 AS200 //连着一个子类AS 65002 //和子…