java实现布隆过滤器(手写和Guava库提供的)

news2024/12/26 23:35:49

目录

前言

布隆过滤器的原理

 插入​编辑

查询

删除

 布隆过滤器优缺点

优点:

缺点:

代码实现

方式一: Google Guava 提供的 BloomFilter 类来实现布隆过滤器

到底经过几次哈希计算

解决缓存穿透 

方式二:手写


前言

        在学习Reids时,关于缓存的三大问题:缓存雪崩、缓存穿透、缓存击穿,其中缓存穿透最好的解决办法就是依靠布隆过滤器,什么是布隆过滤器呢?
布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。——百度百科


布隆过滤器的原理



     布隆过滤器本质上是一个很长的二进制数组,主要用来判断一个数据存不存在数组里,如果存在就用1表示,不存在用0表示,如何表示呢?看下面这张图

 插入

          比如我们把java这行字符存入到布隆过滤器中,首先进过3次哈希函数,分别得到3个哈希值,然后将哈希值根据下标映射到数组中,将对应的0改成1,那么1、3、5这三个位置就存储了java字符。这就是布隆过滤器的插入原理

问题来了:为什么要经历三次哈希计算呢?

其实不一定就是三次哈希计算,这里我只是举例子而已,到底要进行多少次哈希计算。莫急,待会结合实际情况和代码讲解

查询

 讲完插入,再看查询,其实查询和插入原理差不多,当我们查询(java)字符存不存在布隆过滤器中,首先依然进行哈希函数,计算出来的哈希值对应的数组下标,如果对应的1、3、5数组中都是1,那么表示java存在,要是有任意一个位置不为1,那么(java)字符就不存在。

这就是布隆过滤器的主要优势,那么他的缺点之一的就是删除困难,请看详细讲解

删除

             插入java字符时,经过一系列的哈希计算,将下标为1的位置用来储存它,此时jvm字符也经过一系列哈希计算,也得到下标为1的位置来存储它,那么这个1即表示java字符存在又表示jvm字符存在,若要进行删除操作,很难确认你删除的是java还是jvm或许两者同时被删除,所以布隆过滤器不做删除处理

 布隆过滤器优缺点

优点:

1.它是由二进制数组组成,所占空间小

2.基于数组的特性,它的查询和插入是非常快的,它只需要根据哈希计算出来的值找相应的角标就行,时间复杂度是O(m);m=哈希计算的个数,比如存入java字符,进行一个哈希计算就是O(1),进行三个哈希计算就是O(3)。

缺点:

1.不能进行删除操作;

2.存在误判,因为不同的数据计算出来的哈希值可能相同,比如上方的java字符存在于布隆过滤器中,jvm字符不存在,但是他俩的哈希值相同,所以查询jvm时会发生误判。

这个误判是一定存在的,不能避免,但是能减少误判的概率。上代码:

代码实现

方式一: Google Guava 提供的 BloomFilter 类来实现布隆过滤器

导入依赖:

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

 创建 BloomFilter 对象并添加元素

public class BloomFilterCase {

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

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

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


  public static void main(String[] args) {
    // 插入10万样本数据
    for (int i = 0; i < size; i++) {
      bloomFilter.put(i);
    }

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

 private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, fpp);

三个参数:第一个是默认的;存入数据的大小;误判率

 测试一下设置的误判率是否准确

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

 这里误判了947个约等于1000除以10万=1%,所以说这个设置的误判率是正确的,可以多设几个值验证一下

 

可以看到误判率设定的越小,出现误判的概率越小,那是不是把误判率设置的非常非常小就更好呢?这里没办法展示,大家可以自行实践一下,当误判率设定的太小,结果输出延迟会很大,不能实时展示结果,误判率太小,计算量时间长,性能就非常差!

到底经过几次哈希计算

fpp=0.03

 fpp=0.01

 这两张图中

fpp是我们设置的误判率;

numBits是指表示存一百万个int类型数字,需要的位数为7298440,700多万位。理论上存一百万个数,一个int是4字节32位,需要481000000=3200万位。如果使用HashMap去存,按HashMap50%的存储效率,需要6400万位。可以看出BloomFilter的存储空间很小,只有HashMap的1/10左右

numHashFunctions表示需要几个哈希函数去计算数据的哈希值

所以,当fpp越小,所需要的存储空间越大,需要的哈希函数个数越多

解决缓存穿透 

public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://120.48.17.2");
        config.useSingleServer().setPassword("123456");
        //构造Redisson
        RedissonClient redisson = Redisson.create(config);

        RBloomFilter<String> bloomFilter = redisson.getBloomFilter("随便起个名");
        //初始化布隆过滤器:预计元素为100000000L,误差率为3%
        bloomFilter.tryInit(100000000L,0.03);
        //将号码10086插入到布隆过滤器中
        bloomFilter.add("10086");

        //判断下面号码是否在布隆过滤器中
        //输出false
        System.out.println(bloomFilter.contains("123456"));
        //输出true
        System.out.println(bloomFilter.contains("10086"));
    }

方式二:手写

package Bloomfilter;

import java.io.*;
import java.util.BitSet;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by Intellij IDEA.
 * User:   LYX
 * Date:  2023/6/24
 */
public class BloomFilter {
        /**
         * 位数组,用于存储布隆过滤器的状态
         */
        private BitSet bitSet;
        /**
         * 位数组的长度
         */
        private int bitSetSize;
        /**
         * 预期元素数量
         */
        private int expectedNumberOfElements;
        /**
         * 哈希函数数量
         */
        private int numberOfHashFunctions;
        /**
         *  用于生成哈希种子的伪随机数生成器
         */
        private Random random = new Random();

        public BloomFilter(int bitSetSize, int expectedNumberOfElements) {
                this.bitSetSize = bitSetSize;
                this.expectedNumberOfElements = expectedNumberOfElements;

                // 根据公式计算哈希函数数量
                this.numberOfHashFunctions = (int) Math.round((bitSetSize / expectedNumberOfElements) * Math.log(2.0));

                // 创建位数组并初始化所有位为0
                this.bitSet = new BitSet(bitSetSize);
                }

        public void add(Object element) {
                // 对元素进行多次哈希,并将对应的位设置为1
                for (int i = 0; i < numberOfHashFunctions; i++) {
                long hash = computeHash(element.toString(), i);
                int index = getIndex(hash);
                bitSet.set(index, true);
                }
                }

        public boolean contains(Object element) {
                // 对元素进行多次哈希,并检查所有哈希值所对应的位是否都被设置为1
                for (int i = 0; i < numberOfHashFunctions; i++) {
                long hash = computeHash(element.toString(), i);
                int index = getIndex(hash);

                if (!bitSet.get(index)) {
                return false;
                }
                }

                return true;
                }

        private int getIndex(long hash) {
                // 将哈希值映射到位数组的下标(需要确保下标非负)
                return Math.abs((int) (hash % bitSetSize));
                }

        private long computeHash(String element, int seed) {
                // 使用伪随机数生成器生成不同的哈希种子
                random.setSeed(seed);

                // 将元素转换为字节数组,并计算其哈希值
                byte[] data = element.getBytes();
                long hash = 0x7f52bed27117b5efL;

                for (byte b : data) {
                hash ^= random.nextInt();
                hash *= 0xcbf29ce484222325L;
                hash ^= b;
                }

                return hash;
                }
}

测试


/**
 * Created by Intellij IDEA.
 * User:   LYX
 * Date:  2023/6/25
 */
public class BloomfilterTest {
    public static void main(String[] args) {


        List<String> strings = Arrays.asList("JAVA", "JVM", "Redis", "MySQL", "GG");
        BloomFilter filter = new BloomFilter(1000, 5);

        // 将所有字符串添加到布隆过滤器中
        for (String s : strings) {
            filter.add(s);
        }

        String[] queries = {"GG", "MySQL", "JVM", "java"};
        for (String query : queries) {
            System.out.println("是否包含:" + query + "-" + filter.contains(query));
        }
    }
}

运行结果

 


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

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

相关文章

基于PyQt5的桌面图像调试仿真平台开发(1)环境搭建

系列文章目录 基于PyQt5的桌面图像调试仿真平台开发(1)环境搭建 基于PyQt5的桌面图像调试仿真平台开发(2)UI设计和控件绑定 基于PyQt5的桌面图像调试仿真平台开发(3)黑电平处理 基于PyQt5的桌面图像调试仿真平台开发(4)白平衡处理 基于PyQt5的桌面图像调试仿真平台开发(5)…

Git:git merge和git rebase的区别

分支合并 git merge是用来合并两个分支的。比如&#xff1a;将 b 分支合并到当前分支。同样git rebase b&#xff0c;也是把 b 分支合并到当前分支。他们的 「原理」如下&#xff1a; 假设你现在基于远程分支"origin"&#xff0c;创建一个叫"mywork"的分支…

【爬虫】对某某贴吧主页的爬虫分析+源码

1. 网站分析 想要的内容有标题、时间和帖子跳转链接 查看网站源代码&#xff0c;发现想要的内容就在里面&#xff0c;那就好办了&#xff0c;直接上正则&#xff0c;当然beautifulsoup也不是不可以 2. Python源码 import requests import re from prettytable import PrettyTa…

“生鲜蔬”APP的设计与实现

1.引言 在这个科技与网络齐头并进的时代&#xff0c;外卖服务正在飞速发展&#xff0c;人们对外卖APP系统功能需求越来越多&#xff0c;开发APP的人员对自己的要求也要越来越高&#xff0c;要从所做APP外卖系统所实现的功能和用户的需求来对系统进行设计&#xff0c;还需要与当…

基于SpringBoot+vue的人职匹配推荐系统设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

CC1310 CC1310F128RSMR 超低功耗SUB-1GHz 无线 MCU芯片

1 器件概述 1 1 特性 • 微控制器 – 性能强大的 Arm Cortex -M3 处理器 – EEMBCCoreMark评分&#xff1a;142 – EEMBC ULPBench™评分&#xff1a;158 – 时钟速率最高可达 48MHz – 32KB、64KB 和 128KB 系统内可编程闪存 – 8KB 缓存静态随机存取存储器 (SRAM) &#xff…

农业副业产品求购供应发布市场行情VIP会员公众号小程序开源版开发

农业副业产品求购供应发布市场行情VIP会员公众号小程序开源版开发 后台一键同步全国近200家农产品批发市场商品包括&#xff0c;蔬菜、水果、水产、粮油和农副产品等的价格。 前端VIP权益功能&#xff0c;开通VIP会员后&#xff0c;可以开启VIP会员标识。可无限制查看全国市场…

Scrapy框架之Mongo安装和与关系型数据库比较

目录 Windows安装与启动MongoDB 下载 启动MongoDB 通过命令启动 脚本 快速学习方法 与关系型数据库比较 什么是BSON Windows安装与启动MongoDB 下载 企业版-收费 社区版-免费 下载Mongodb Download MongoDB Community Server | MongoDB 选择版本 稳定版5.0.9 选择平台…

前端工程化 | vue3+ts+jsx+sass+eslint+prettier 配置化全流程

起因&#xff1a; 前端开发是一个工程化的流程。 包括持续集成、持续部署。 我认为集成 的第一方面就是开发&#xff0c;在前端项目开发中&#xff0c;需要保证代码格式规范的统一、代码质量、提交的规划。而这些要求需要通过各种插件来保证规范化和流程化开发。 如何配置这…

大数据的金融数据读取及分析(二)

一、注册和获取token 参考大数据的金融数据读取及分析&#xff08;一&#xff09;大数据的金融数据读取及分析&#xff08;-&#xff09;_石工记的博客-CSDN博客 二、获取股市信息 需注意的是&#xff0c;利用tushare接口获取部分信息时对积分有不同的要求&#xff0c;积分不…

后室主题 Game Jam

在后室主题 Game Jam 中探索无尽的深渊&#xff01; 向所有富有冒险精神的游戏开发者和创作者发出召集令&#xff01;准备好潜入未知领域&#xff0c;将令人毛骨悚然的后室之谜变为现实吗&#xff1f;加入我们&#xff0c;参加与 Game Maker 合作举办的令人振奋的游戏竞赛吧&am…

【C语言】GNU make 和 Makefile :构建工具与构建描述文件的力量

本文将详细介绍make和Makefile&#xff0c;它们是软件开发中常用的构建工具和构建描述文件。本文将探讨make的作用、原理和用法&#xff0c;以及Makefile的结构、语法和常见用法。通过了解这些工具&#xff0c;开发者可以更高效地管理和构建复杂的软件项目。 引言一、make1.1 m…

Java8新特性详解

陈老老老板 说明&#xff1a;新的专栏&#xff0c;本专栏专门讲Java8新特性&#xff0c;把平时遇到的问题与Java8的写法进行总结&#xff0c;需要注意的地方都标红了&#xff0c;一起加油。 本文是介绍Java8新特性与常用方法&#xff08;此篇只做大体介绍了解&#xff0c;之后会…

使用OpenCV工具包实现人脸检测与人脸识别,包括传统视觉和深度学习方法(最全整理!)

使用OpenCV工具包实现人脸检测与人脸识别&#xff08;最全整理&#xff01;&#xff09; OpenCV实现人脸检测OpenCV人脸检测方法基于Haar特征的人脸检测Haar级联检测器预训练模型下载Haar 级联分类器OpenCV-Python实现 基于深度学习的人脸检测传统视觉方法与深度学习方法对比 O…

three.js 最小环境搭建

完整目录: 1、html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><st…

专利优先权应在什么时候提出

专利优先权要求应当在3个月内提交第一次提出的专利申请文件的副本&#xff1b;未提出书面声明或者逾期未提交专利申请文件副本的&#xff0c;视为未要求优先权。 申请人就相同主题的发明或实用新型在外国第一次提出专利申请之日起十二个月内&#xff0c;或者就相同主题的外观设…

【STM32智能车】智能车专题知识补充

【STM32智能车】智能车专题知识补充 智能车专题智能车的定义和发展历程。智能车的特点和优势。智能车的关键技术智能车的应用场景&#xff0c;如出租车、物流配送、公共交通等。智能车在环境保护、交通安全、经济发展等方面的作用。智能车发展面临的挑战和机遇 智能车专题 本专…

使用 Elasticsearch 1

了解如何创建索引&#xff0c;添加&#xff0c;删除&#xff0c;更新文档 参考文档 开始使用 Elasticsearch 1 本文用到Elasticsearch和Kibana 可以看之前的两篇先安装好 Elasticsearch 安装 Kibana安装 Elasticsearch 里的接口都是通过 REST 接口来实现的。 GET 读取数…

Scrapy框架之MongoDB通过配置文件管理参数--Linux安装MongoDB--图形管理工具

目录 MongoDB通过配置文件 问题 解决方案 步骤 提示 Linux安装MongoDB 环境 下载依赖与安装包 解压安装 MongoDB GUI管理工具 独立软件GUI软件 Robo 3T使用 VSCode集成GUI插件 MongoDB通过配置文件 问题 启动MongoDB时&#xff0c;编写参数太麻烦 解决方案 通过配…

【PCL】(三)读写PCD文件

文章目录 &#xff08;三&#xff09;读写PCD文件写读 &#xff08;三&#xff09;读写PCD文件 写 首先&#xff0c;创建一个名为pcd_write.cpp的文件&#xff0c;并在其中写入以下代码&#xff1a; #include <iostream> #include <pcl/io/pcd_io.h> #include &…