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

目录

前言

布隆过滤器的原理

 插入​编辑

查询

删除

 布隆过滤器优缺点

优点:

缺点:

代码实现

方式一: 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");//构造RedissonRedissonClient redisson = Redisson.create(config);RBloomFilter<String> bloomFilter = redisson.getBloomFilter("随便起个名");//初始化布隆过滤器:预计元素为100000000L,误差率为3%bloomFilter.tryInit(100000000L,0.03);//将号码10086插入到布隆过滤器中bloomFilter.add("10086");//判断下面号码是否在布隆过滤器中//输出falseSystem.out.println(bloomFilter.contains("123456"));//输出trueSystem.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));// 创建位数组并初始化所有位为0this.bitSet = new BitSet(bitSetSize);}public void add(Object element) {// 对元素进行多次哈希,并将对应的位设置为1for (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) {// 对元素进行多次哈希,并检查所有哈希值所对应的位是否都被设置为1for (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.hqwc.cn/news/2772.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Springboot 核心注解和基本配置解读

目录 1. Springboot 入门与原理 1.1 Springboot 简介 1.1.1 什么是Springboot 1.1.2 Springboot 主要优点 1.2 Springboot 相关注解 1.2.1 元注解 1.2.1.1 Target 1.2.1.2 Retention 1.2.2 Configuration 1.2.3 Import 1.2.3.1 直接注入 1.2.3.2 实现 ImportSelector…

基于Java+Vue前后端分离开放式教学评价管理系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

关于u(x,t)=f(x)*g(t)形式证明的思考

突然想起来&#xff0c;二维高斯函数是可以拆分成两个一维高斯函数相乘的&#xff1a; 原来在学概率论的时候&#xff0c;证明过&#xff0c;这只能说高斯函数可以&#xff0c;这是一个思路。 一维波动函数应该也是这个套路。 那么还有没有其他函数可以如此&#xff0c;有如此…

burpsuite踩坑(一)

今天在使用burpsuite的时候&#xff0c;能抓到https或者http的包。 但是repeater模块无法使用&#xff0c;而且放行包之后&#xff0c;会出现提示。 搞了半天&#xff0c;以为是证书的问题&#xff0c;或者是burp汉化版的原因&#xff0c;还把汉化版的burp给删除了。 发现都…

HOT30-两两交换链表中的节点

leetcode原题链接&#xff1a;两两交换链表中的节点 题目描述 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即只能进行节点交换&#xff09;。 示例 1&#xff1a; 输入&a…

嵌入式系统的不同方向及优化策略

当涉及到嵌入式系统开发时&#xff0c;可以根据具体的应用需求选择不同的方向进行优化。以下是一些常见的嵌入式系统方向及其特点&#xff1a; 单片机方向&#xff1a;这个方向主要针对使用单片机作为核心的嵌入式系统开发。单片机资源有限&#xff0c;适用于简单的控制任务&am…

u盘ntfs和fat32哪个好 把u盘改成ntfs有什么影响

u盘在日常生活中的使用频率很高&#xff0c;许多用户在选购u盘时很少会注意到u盘格式&#xff0c;但u盘的格式对u盘的使用有很大影响。u盘格式有很多&#xff0c;常见的有ntfs和fa32&#xff0c;u盘ntfs和fat32哪个好&#xff1f;这要看u盘的使用场景。把u盘改成ntfs有什么影响…

LeetCode 2501 数组中最长的方波 Java

方法一&#xff0c;哈希表枚举 构造哈希集合&#xff0c;记录出现过的数字枚举遍历 import java.util.HashSet; import java.util.Set;class Solution {public int longestSquareStreak(int[] nums) {//构造哈希表集合&#xff0c;记录出现过的数字&#xff0c;转long型&…

[Pytorch]导数与求导

文章目录 导数与求导一. 标量 向量 矩阵 的导数二.Pytorch中的反向求导.backward()三.非标量求导 导数与求导 一. 标量 向量 矩阵 的导数 标量&#xff0c;向量&#xff0c;矩阵间求导后的形状&#xff1a; y\x标量x(1)向量 x(n,1)矩阵 X(n,k)标量y(1)(1)(1,n)(k,n)向量 y(m…

记录 Linux centos 安装tomact遇到的问题

如果在安装时 觉得自己什么都安装好了&#xff0c;什么也设置好了&#xff0c;包括阿里云的安全组&#xff0c;但是依旧不能进行访问Tomact的主页&#xff0c;你可以查看一下 catalina.out这个文件&#xff0c;出现以下错误这表示 tomact和Java本版有冲突所以一直无法访问&…

【开源与项目实战:开源实战】84 | 开源实战四(上):剖析Spring框架中蕴含的经典设计思想或原则

在 Java 世界里&#xff0c;Spring 框架已经几乎成为项目开发的必备框架。作为如此优秀和受欢迎的开源项目&#xff0c;它是我们源码阅读的首选材料之一&#xff0c;不管是设计思想&#xff0c;还是代码实现&#xff0c;都有很多值得我们学习的地方。接下来&#xff0c;我们就详…

若依——限流(rateLimiter)(lua脚本与令牌桶)

在原版若依当中使用了lua脚本进行限流 注意这里进行了bean的托管&#xff0c;因此我们才能使用limitScript 关于lua脚本的解释 在若依的Plus版本当中&#xff0c;结合了Redisson使用令牌桶进行限流。由于Redisson已经封装好了&#xff0c;使用起来比较简单&#xff0c;更多…