详解布隆过滤器及其模拟实现

news2025/3/18 8:01:49

目录

布隆过滤器

引入

概念

工作原理

模拟实现布隆过滤器

哈希函数集

布隆过滤器基本框架

add函数(添加到布隆过滤器中)

contains函数(判断是否存在该值)

完整代码

布隆过滤器的删除

布隆过滤器的误判率

布隆过滤器的优点

布隆过滤器的缺点

布隆过滤器的应用场景


布隆过滤器
引入

我们如何判断一个元素是否在一个集合中?

我们可能会想到将集合中所有的元素加载到内存中,并存储到哈希表中,这样就能很容易判断出一个元素是否在一个集合中,但是这只能处理集合元素数量并不大的场景,对于集合中有海量的元素时,是行不通的,那么该如何解决呢?

我们知道,使用哈希表来存储,优点是能够快速查找,缺点是浪费空间;使用位图来存储,优点是能够快速查找,也不浪费空间,但是缺点是一般只能处理整形,对于较复杂的内容就无法处理了。

将哈希表和位图相结合,得到了一种新的结构,即“布隆过滤器”,它能够解决掉上面的问题。

概念

布隆过滤器(Bloom Filter)是一种空间效率极高的概率型数据结构,由Burton Howard Bloom在1970年提出。它主要用于判断一个元素是否可能属于某个集合,而不支持直接获取集合中的所有元素。布隆过滤器的基本结构是一个固定长度的位数组/位图(Bit Array)和一组哈希函数(Hash Functions)。它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。

概念图:

工作原理

(1)初始时,位图中所有位置的值设置为0.

(2)当有值通过哈希函数映射到该位置时,值才置为1。

(3)通过判断某个值哈希映射得到的每个位置的值是否不为0,就能知道该值“一定不存在”或者“可能存在”。

模拟实现布隆过滤器
哈希函数集
class SimpleHash {

    public int cap;//当前容量
    public int seed;//随机

    public SimpleHash(int cap,int seed) {
        this.cap = cap;
        this.seed = seed;
    }

    //根据seed不同 创建不能的哈希函数
    int hash(String key) {
        int h;
        //(n - 1) & hash
        return (key == null) ? 0 : (seed * (cap-1)) & ((h = key.hashCode()) ^ (h >>> 16));
    }

}

上面的哈希函数参照了HashMap的原码:

布隆过滤器基本框架
public class MyBloomFilter {

    public static final int DEFAULT_SIZE = 1 << 20;
    //位图
    public BitSet bitSet;

    public static final int[] seeds = {5,7,11,13,27,33};

    public SimpleHash[] simpleHashes;

    public MyBloomFilter() {
        bitSet = new BitSet(DEFAULT_SIZE);
        simpleHashes = new SimpleHash[seeds.length];
        for (int i = 0; i < simpleHashes.length; i++) {
            simpleHashes[i] = new SimpleHash(DEFAULT_SIZE,seeds[i]);
        }
    }
}
add函数(添加到布隆过滤器中)

将要添加的值使用若干哈希函数进行映射,并将映射位置的值置为1.

public void add(String val) {
    //让若干个哈希函数  分别处理当前的数据
    for (SimpleHash simpleHash : simpleHashes) {
        int index = simpleHash.hash(val);
        //把他们 都存储在位图当中即可
        bitSet.set(index);
    }
}
contains函数(判断是否存在该值)

将要添加的值使用若干哈希函数进行映射,并以此判断这些位置的值是否为0,若存在为0的情况,则该值一定不存在,否则,是可能存在,因为可能存在其他值映射到这些位置的情况。

public boolean contains(String val) {
    for (SimpleHash simpleHash : simpleHashes) {
        int index = simpleHash.hash(val);
        //只要有1个为 0     那么一定不存在
        boolean flg = bitSet.get(index);
        if(!flg) {
            return false;
        }
    }
    return true;
}
完整代码
import java.util.BitSet;

class SimpleHash {

    public int cap;//当前容量
    public int seed;//随机

    public SimpleHash(int cap,int seed) {
        this.cap = cap;
        this.seed = seed;
    }

    //根据seed不同 创建不能的哈希函数
    int hash(String key) {
        int h;
        //(n - 1) & hash
        return (key == null) ? 0 : (seed * (cap-1)) & ((h = key.hashCode()) ^ (h >>> 16));
    }

}
public class MyBloomFilter {

    public static final int DEFAULT_SIZE = 1 << 20;
    //位图
    public BitSet bitSet;

    public static final int[] seeds = {5,7,11,13,27,33};

    public SimpleHash[] simpleHashes;

    public MyBloomFilter() {
        bitSet = new BitSet(DEFAULT_SIZE);
        simpleHashes = new SimpleHash[seeds.length];
        for (int i = 0; i < simpleHashes.length; i++) {
            simpleHashes[i] = new SimpleHash(DEFAULT_SIZE,seeds[i]);
        }
    }

    public void add(String val) {
        //让若干个哈希函数  分别处理当前的数据
        for (SimpleHash simpleHash : simpleHashes) {
            int index = simpleHash.hash(val);
            //把他们 都存储在位图当中即可
            bitSet.set(index);
        }
    }

    public boolean contains(String val) {
        for (SimpleHash simpleHash : simpleHashes) {
            int index = simpleHash.hash(val);
            //只要有1个为 0     那么一定不存在
            boolean flg = bitSet.get(index);
            if(!flg) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        MyBloomFilter myBloomFilter = new MyBloomFilter();
        myBloomFilter.add("hello");
        myBloomFilter.add("hello2");

        System.out.println(myBloomFilter.contains("hello"));
        System.out.println(myBloomFilter.contains("hello3"));
    }
}

运行结果:

布隆过滤器的删除

布隆过滤器不支持直接删除,因为在删除一个元素时,可能会影响其它元素。

例如,通过上图可以看到,baidu和tencent有相同的映射位置,因此如果直接删除某个元素,可能会影响到其它元素。

那么有没有办法使得布隆过滤器支持删除操作呢?

有,比如给每个比特位附带一个计数器,当有元素映射到该位置时,该位置的计数器进行++,当删除元素时,只需要将对应位置的计数器进行- -。

缺陷:

1.无法确认元素是否真的在布隆过滤器中,即可能会判断失误。

2.存在计数回绕,即溢出。

布隆过滤器的误判率

n:布隆过滤器最大处理的元素的个数
P:希望的误差率
m:布隆过滤器的bit位数目
k:哈希函数的个数

布隆过滤器的优点

1. 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关
2. 哈希函数相互之间没有关系,方便硬件并行运算
3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
4. 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势
5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算

布隆过滤器的缺点

1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)
2. 不能获取元素本身
3. 一般情况下不能从布隆过滤器中删除元素
4. 如果采用计数方式删除,可能会存在计数回绕问题 

布隆过滤器的应用场景

1.缓存穿透防护:在分布式缓存系统如Redis或Memcached中,用于避免缓存穿透问题。当一个请求试图访问数据库中的某个不存在的键时,如果直接去数据库查询会增加数据库压力。通过在前端部署一个布隆过滤器,可以预先判断该键很可能不存在于数据库中,从而避免对数据库发起无效请求。
2.URL去重:在爬虫抓取网页或者日志分析中,用于URL去重,确保不会重复抓取相同的页面或记录。
3.大数据处理:在Hadoop等框架中,用来过滤掉重复的数据块或者记录,减少计算和存储负担。
4.垃圾邮件过滤:在电子邮件系统中,用于快速判断收到的邮件是否可能来自已知的垃圾邮件发送者。
5.异常事件检测:当大量事件流经系统时,可以用于快速识别并过滤出已知异常事件,降低报警系统误报率。
6.个性化推荐:在个性化推荐系统中,用于快速排除用户已经浏览过或者不感兴趣的内容。
数据库辅助索引:对于大型数据库,可以利用布隆过滤器作为辅助索引结构,提前过滤掉大部分肯定不在结果集中的查询条件,减轻主索引的压力。
7.内容检测:在社交网络中,用于快速检测用户上传的内容是否存在违规信息,或是检查用户ID、账号是否存在黑名单中。

  • 欢迎大家来访问我的博客主页----》点击链接
  • 欢迎大家订阅我的数据结构专栏----》点击链接

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

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

相关文章

element-plus中DatePicker 日期选择器组件的使用

1.选择某一天 代码&#xff1a; <el-date-pickerv-model"invoice_date"type"date"placeholder"请选择日期"style"width: 200px;"clearable /> 运行效果&#xff1a; 问题所在&#xff1a;这个数据的格式不是我们后端需要的那种&…

SvelteKit 最新中文文档教程(4)—— 表单 actions

前言 Svelte&#xff0c;一个语法简洁、入门容易&#xff0c;面向未来的前端框架。 从 Svelte 诞生之初&#xff0c;就备受开发者的喜爱&#xff0c;根据统计&#xff0c;从 2019 年到 2024 年&#xff0c;连续 6 年一直是开发者最感兴趣的前端框架 No.1&#xff1a; Svelte …

力扣hot100二刷——二叉树

第二次刷题不在idea写代码&#xff0c;而是直接在leetcode网站上写&#xff0c;“逼”自己掌握常用的函数。 标志掌握程度解释办法⭐Fully 完全掌握看到题目就有思路&#xff0c;编程也很流利⭐⭐Basically 基本掌握需要稍作思考&#xff0c;或者看到提示方法后能解答⭐⭐⭐Sl…

字符串哈希从入门到精通

一、基本概念 字符串哈希是将任意长度的字符串映射为固定长度的哈希值&#xff08;通常为整数&#xff09;的技术&#xff0c;核心目标是实现O(1)时间的子串快速比较和高效查询。其本质是通过数学运算将字符串转换为唯一性较高的数值&#xff0c;例如&#xff1a; ​​​​​​…

C语言:编程设计猜数游戏

先由计算机想一个数给用户猜&#xff0c;如果猜对了&#xff0c;提示“right&#xff01;”&#xff0c;猜错了&#xff0c;提示“wrong&#xff01;及大小” 思路&#xff1a;用随机函数rand&#xff08;&#xff09;取到计算机想的数 代码&#xff1a; #include <stdio.…

win10 c++ VsCode 配置PCL open3d并显示

win10 c VsCode配置PCL open3d并显示 一、效果图二、配置步骤2.1 安装vscode2.2 pcl-open3d配置2.3 vscode中设置 三、测试代码四、注意事项及后续 一、效果图 二、配置步骤 2.1 安装vscode vscode下载链接 下载中文插件、c相关插件 2.2 pcl-open3d配置 1&#xff09;下载…

Vala 开发环境搭建

介绍 Vala 是一种使用现代高级抽象的编程语言&#xff0c;与用 C 语言编写的应用程序和库相比&#xff0c;没有施加额外的运行时要求&#xff0c;也不需要使用不同的 ABI。 Vala 使用 GObject 类型系统&#xff0c;并具有额外的代码生成例程&#xff0c;使面向 GNOME 堆栈变得简…

【网页】自制流光卡片

概述 小红书有个博主自己搞的笔记排版工具叫“流光卡片”&#xff0c;类似的还有个Markdown排版工具叫MD2Card。 我这个版本类似&#xff0c;但是自己写的东西&#xff0c;控制性更好。 初期就写了个静态页面&#xff0c;后期结合Godot快速生成&#xff0c;并可能结合JS库&a…

CSP-J/S冲奖第18天:真题解析

解题步骤 读取输入&#xff1a;首先读取整数n&#xff0c;然后读取n个正整数并存储在一个数组或容器中。 排序数组&#xff1a;对数组进行排序&#xff0c;以便后续使用双指针法高效查找。 遍历数组&#xff1a;对于每个数target&#xff0c;检查是否存在另外两个不同的数a和…

【linux】虚拟机执行sudo yum isntall perl报错 could not retrieve mirrorlist htt:

项目场景&#xff1a; 提示&#xff1a;虚拟机安装拓展包&#xff0c;sudo yum install perl Virtualbox 在不安装增强功能扩展的情况下, 无法自适应分辨率和共享剪切板等操作 问题描述 原因分析&#xff1a; 提示&#xff1a;这里填写问题的分析&#xff1a; 出现这个错误是因…

旅游类小程序界面设计

产品概述 艾啦游是一款互联网旅游类小程序&#xff0c;致力于国内精品旅游&#xff0c;以及拥有自由行、专属热榜单、出行攻略等诸多功能&#xff0c;汇聚了许多国内的人气景点&#xff0c;与诸多城市的酒店也保持合作&#xff0c;打造一体式旅行服务&#xff0c;更有不断上新…

DQN 玩 2048 实战|第三期!优化网络,使用GPU、Env奖励优化

视频讲解&#xff1a; DQN 玩 2048 实战&#xff5c;第三期&#xff01;优化网络&#xff0c;使用GPU、Env奖励优化 1. 仅考虑局部合并奖励&#xff1a;目前的奖励只设置为合并方块时获得的分数&#xff0c;只关注了每一步的即时合并收益&#xff0c;而没有对最终达成 2048 这个…

【python】http post 在body中传递json数据 以发送

http post 在body中传递json数据 以发送&#xff0c;json的格式非常重要这里要传递json对象&#xff0c;而不是一个json字符串 传递post一个 JSON 字符串 是ok的 是的&#xff0c; {"rsource_rhythm_action_list": {"name": "AI_\\u6708\\u4eae\\u…

[贪心算法]-最大数(lambda 表达式的补充)

1.解析 我们一般使用的排序比较大小都是 a>b 那么a在b的前面 ab 无所谓 a<b a在b的后面 本题的排序则是 ab>ba 那么a在b的前面 abba 无所谓 ab<ba a在b的后面 2.代码 class Solution { public:string largestNumber(vector<int>& nums) {//1.先把所有…

C语言 —— 此去经年梦浪荡魂音 - 深入理解指针(卷二)

目录 1. 数组名与地址 2. 指针访问数组 3.一维数组传参本质 4.二级指针 5. 指针数组 6. 指针数组模拟二维数组 1. 数组名与地址 我们先看下面这个代码&#xff1a; int arr[10] { 1,2,3,4,5,6,7,8,9,10 };int* p &arr[0]; 这里我们使用 &arr[0] 的方式拿到了数…

python实现简单的图片去水印工具

python实现简单的图片去水印工具 使用说明&#xff1a; 点击"打开图片"选择需要处理的图片 在图片上拖拽鼠标选择水印区域&#xff08;红色矩形框&#xff09; 点击"去除水印"执行处理 点击"保存结果"保存处理后的图片 运行效果 先简要说明…

使用dify+deepseek部署本地知识库

使用difydeepseek部署本地知识库 一、概述二、安装windows docker desktop1、确认系统的Hyper-v功能正常启用2、docker官网下载安装windows客户端3、安装完成后的界面如下所示 三、下载安装ollama四、部署本地deepseek五、本地下载部署dify5.1 下载dify的安装包5.2 将dify解压到…

【算法day13】最长公共前缀

最长公共前缀 https://leetcode.cn/problems/longest-common-prefix/submissions/612055945/ 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 “”。 class Solution { public:string longestCommonPrefix(vector<string&g…

Java高频面试之集合-13

hello啊&#xff0c;各位观众姥爷们&#xff01;&#xff01;&#xff01;本baby今天来报道了&#xff01;哈哈哈哈哈嗝&#x1f436; 面试官&#xff1a;为什么 hash 函数能降哈希碰撞&#xff1f; 哈希函数通过以下核心机制有效降低碰撞概率&#xff0c;确保不同输入尽可能映…

RGV调度算法(三)--遗传算法

1、基于时间窗 https://wenku.baidu.com/view/470e9fd8b4360b4c2e3f5727a5e9856a57122693.html?_wkts_1741880736197&bdQuery%E7%8E%AF%E7%A9%BF%E8%B0%83%E5%BA%A6%E7%AE%97%E6%B3%95 2.2019年MathorCup高校数学建模挑战赛B题 2019-mathorcupB题-环形穿梭机调度模型&a…