手撕布隆过滤器:原理解析与面试心得

前言

说来话长,话来说长。前些天我投了一些日常实习的简历,结果足足等了两个礼拜才收到面试通知,看来如今的行情确实是挺紧张的。当时我是满怀信心去的,心想这次一定要好好拷打面试官一番,结果没想到,自我介绍刚一结束,面试官就要开始拷打我了,直接让我手撕布隆过滤器?我当时心里可真是:我勒个豆啊(此处省略无数心里话)。虽然我对它的原理是了解的,但之前还真没有亲手写过。就这样,我开始了这次的手撕布隆过滤器之旅。

一、什么是布隆过滤器

【面试官】:布隆过滤器是什么?你给我讲讲吧。

【自信的我开始吟唱】:

  • 布隆过滤器是一种基于哈希算法的位数组数据结构,主要用于高效地检测一个元素是否在一个集合中。尽管它可以极大地节省存储空间,但由于其允许一定的误报率(即可能会错误地报告一个元素存在于集合中),因此在使用时需要权衡误报的可能性。

  • 当数据量增加时,传统的集合数据结构(如哈希表或列表)会占用大量的内存。而布隆过滤器通过使用多个哈希函数和一个固定长度的位数组来减少内存使用。其主要用途在于能够快速地判断某项元素是否属于特定集合,特别适用于需要快速去重检查的场景,并且它还可以有效地防止缓存穿透攻击。

布隆过滤器原理图

二、实现原理

【面试官点点头,心里暗想着必须要拷打到你】:你跟我讲讲它是如何实现的吧。

【无比自信的我】:

  • 底层原理是通过一个bitmap二进制位数组表示集合来实现的。数组初始位都是0,当添加元素时,会通过多个哈希函数将元素映射到位数组中的多个位置,并将这些位设为1。需要查询某个元素是否存在布隆过滤器时,只需对该元素进行多次哈希,并判断位数组对应位置上的位是否为1即可。
布隆过滤器实现图

三、问题

3.1、 误判率

【面试官追问】:那它二进制位数组,不会有不同元素映射到相同的位置吗?

【我】:

  • 这种情况发生是因为哈希函数的冲突,使得不同元素被映射到相同的位置,导致布隆过滤器的误判率问题。如下图所示:
哈希冲突图
  • 对于误判率,有一个公式可以计算:k:哈希次数,n:元素个数,m:bit数组长度
    p ( n , m , k ) = ( 1 − e − k n / m ) k p(n,m,k) = (1 - e^{-kn/m})^k p(n,m,k)=(1ekn/m)k
    同时还有一个便捷的网站方便我们计算:https://hur.st/bloomfilter/
    使用方法如下:
误判率计算图
  • 通过上述公式我们知道,为了降低误判率,可以采取以下措施:
    1. 增加位数组大小:如果位数组的大小增加,哈希函数分布的碰撞概率降低,从而降低误判率。
    2. 增加哈希函数数量:通过对元素值进行多次哈希操作,得到相对离散的值,但过多的哈希函数会导致效率下降。
    3. 控制插入元素的数量:布隆过滤器的容量是有限的,超过容量后误判率会快速升高。因此,通过限制数据量在布隆过滤器某一阈值比例内,可以保持较低的误判率。尽管如此,布隆过滤器占用的空间相较于其他数据结构还是非常小的。

3.2、 不可删除

【面试官】:除了这个,布隆过滤器还有什么问题吗?

【我】:

  • 布隆过滤器的另一个缺点是无法删除元素。一旦某个元素被添加,无法准确将其从位数组中移除,否则可能会影响其他元素的判定。但是,有一种改进版本的布隆过滤器,称为计数布隆过滤器或动态布隆过滤器(如布谷鸟过滤器),可以在一定程度上解决这一问题。

四、使用场景

【面试官,心想答得还不错,基本都覆盖了】:那你说说布隆过滤器有哪些使用场景吧。

【我】:

  • 常用的话,一般有以下几个场景:
    1. 快速判重:在大规模数据处理场景中,布隆过滤器可以快速判定某个数据是否已经出现,例如用于网页爬虫中避免抓取重复的网页。
    2. 缓存穿透:布隆过滤器可以用于防止缓存穿透。当查询的数据不在缓存中,也不在数据库中时,直接利用布隆过滤器判断该数据是否存在,从而减少对数据库的压力。
    3. 推荐系统中的候选生成:在推荐系统中,布隆过滤器可以用来排除已经推荐过的项目,从而提高推荐的多样性和新颖性。
    4. 恶意域名检测:在网络安全领域,可以使用布隆过滤器来存储已知的恶意域名列表,快速判断新遇到的域名是否可能存在安全风险。
    5. 大数据预筛选:在处理大规模数据集时,布隆过滤器可用于预筛选,减少后续处理的数据量,提高处理效率。

五、如何使用

【面试官】:那你讲讲你在项目中是如何实现的吧。

【我】:

  • 布隆过滤器在 Guava 和 Redisson 中都已经有了实现(这些实现可以直接在网上找到教程,这里就不做具体演示了),我们在使用时可以直接使用这些现有的实现类。

  • 由于我在多个服务下需要实现分布式的共享布隆过滤器数据,所以我选择了使用 Redis 实现布隆过滤器。这样可以确保多个服务之间能够共享布隆过滤器的状态,从而提高系统的整体性能和一致性。如果是单机环境下,直接使用 Guava 的实现即可。

六、Java 手撕

【面试官,心想竟然难不倒你】:那你Java手撕一个吧。

【我】:啊?(然后默默开启了手撕)

在使用Java实现布隆过滤器时,可以借助 BitSet 或自定义的 BitMap 来存储位数组。同时,多个哈希函数可以通过 MessageDigest 或其他常见的哈希库来实现。以下是一个Java布隆过滤器的示例实现概述:

package com.example.provider.utils;import java.util.BitSet;
import java.util.Random;public class BloomFilter {// 一个位数组,用于存储数据private final BitSet bitSet;// 位数组的大小private final int bitSetSize;// 使用的哈希函数数量private final int numHashFunctions;private final Random random;// 布隆过滤器构造函数public BloomFilter(int capacity, int numHashFunctions) {this.bitSetSize = capacity;this.numHashFunctions = numHashFunctions;this.bitSet = new BitSet(bitSetSize);this.random = new Random();}/** 添加元素到布隆过滤器* - 将元素通过多个哈希函数进行散列。* - 将这些哈希值映射到位数组中对应的索引位置,并将这些位置的值设为1。* @param value*/public void add(String value) {for (int i = 0; i < numHashFunctions; i++) {int hash = hash(value, i);bitSet.set(Math.abs(hash % bitSetSize), true);}}/** 检查元素是否存在* - 将元素通过同样的哈希函数散列。* - 检查这些散列值对应的位数组位置是否都为1,* 如果是,则说明元素可能存在(但不保证);如果有任意位置为0,则说明元素一定不存在。* @param value* @return*/public boolean mightContain(String value) {for (int i = 0; i < numHashFunctions; i++) {int hash = hash(value, i);if (!bitSet.get(Math.abs(hash % bitSetSize))) {return false;}}return true;}// 哈希函数,用于生成多个不同的哈希值private int hash(String value, int seed) {random.setSeed(value.hashCode() + seed);return random.nextInt();}// 测试布隆过滤器public static void main(String[] args) {int capacity = 1000; // 位数组大小int numHashFunctions = 5; // 使用的哈希函数数量BloomFilter bloomFilter = new BloomFilter(capacity, numHashFunctions);// 添加元素bloomFilter.add("apple");bloomFilter.add("banana");// 检查元素是否存在System.out.println(bloomFilter.mightContain("apple"));  // 可能存在System.out.println(bloomFilter.mightContain("banana")); // 可能存在System.out.println(bloomFilter.mightContain("grape"));  // 一定不存在}
}
image-20241018224218570

七、总结与思考

【最后,面试官】:时间差不多够了,我们面试就到此为止吧,你先回去等通知吧。

【我】:???

7.1、总结

通过本文,相信大家已经了解了布隆过滤器的基本概念及其在面试中的应用技巧。我们再来谈谈布隆过滤器的设计吧, 它巧妙地利用了位数组和哈希函数的特点,通过将元素映射到位数组中,实现了内存的有效利用。这种设计不仅减少了内存占用,还提高了查询速度,非常适合需要快速去重检查的场景。

7.2、思考

同时,布隆过滤器的设计与应用实现也可以给我们带来以下方面的思考:

  1. 创新思维:布隆过滤器提供了一种全新的视角来看待问题解决方式,它鼓励我们突破传统数据结构的局限,探索更加灵活且高效的解决方案。这种思维方式能够激励我们在面对各类问题时,勇于探索并尝试不同的方法。
    • 事实上,布隆过滤器可以视为是对哈希表的一种创新性应用,它利用哈希表 O(1) 的时间复杂度,并通过位数组来大幅降低内存占用,实现了低空间占用与高响应速度的完美结合。
  2. 资源利用效率:布隆过滤器的设计理念是尽可能地利用有限的存储空间,这启示我们在处理大数据量的问题时,应考虑如何更有效地管理内存或磁盘空间。
    • 例如,由于系统内存极为宝贵,Redis 就采用了多种高空间利用率的数据结构,实现了在小容量内存下的高效存储。
  3. 性能优化:在一些对查询速度要求极高的场景下,布隆过滤器可以显著提高系统的响应速度。这让我们意识到,在设计系统时,应当根据应用场景的具体需求来选择最合适的数据结构和算法。
    • 例如,在 MySQL 中,为了适应磁盘 IO 查询的需求,选择了 B+ 树作为索引结构;而在 Redis 中,则采用了跳表来实现有序集合(Zset),以平衡内存使用与访问效率之间的关系。
  4. 权衡取舍:使用布隆过滤器需要接受一定的误报率,这实际上是在准确性和存储成本之间的一种折衷。这种思想可以推广到其他领域,比如在开发软件时,我们需要在功能全面性与系统复杂度之间找到最佳的平衡点。没有所谓的“最好”方案,只有最适合特定情境的解决方案。
  5. 应用范围扩展:除了布隆过滤器本身的应用外,其背后的思想还可以应用到其他领域。例如,当需要在短时间内处理大量数据时,可以借鉴类似的概念来创建更有效的缓存机制或预筛选机制,从而提升整体系统的性能与效率。

最后,本文到这里就结束了,希望能对你有所帮助。如果觉得内容有用,请给予支持。另外,有兴趣的话可以进一步了解布谷鸟过滤器等改进版本,它们在某些场景下提供了更为灵活的解决方案。

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

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

相关文章

腰背肌筋膜炎有哪些治疗方法

腰背肌筋膜炎主要表现为腰背部的疼痛、酸胀、僵硬、活动受限等症状。在疾病初期&#xff0c;症状可能相对较轻&#xff0c;通过休息、保暖、适当的物理治疗等&#xff0c;往往可以缓解症状&#xff0c;此时病情不算严重。如果患者不重视&#xff0c;继续保持不良的生活习惯&…

微服务架构 --- 使用RabbitMQ进行异步处理

目录 一.什么是RabbitMQ&#xff1f; 二.异步调用处理逻辑&#xff1a; 三.RabbitMQ的基本使用&#xff1a; 1.安装&#xff1a; 2.架构图&#xff1a; 3.RabbitMQ控制台的使用&#xff1a; &#xff08;1&#xff09;Exchanges 交换机&#xff1a; &#xff08;2&#…

什么是不同类型的微服务测试?

大家好&#xff0c;我是锋哥。今天分享关于【什么是不同类型的微服务测试&#xff1f;】面试题&#xff1f;希望对大家有帮助&#xff1b; 什么是不同类型的微服务测试&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 微服务架构中的测试可以分为多种类…

WPF基础权限系统

一.开发环境 VisualStudio 2022NET SDK 8.0Prism 版本 8.1.97Sqlite 二. 功能介绍 WPF 基础权限系统&#xff0c;是一个支持前后端分离设计的 客户端(C/S)项目&#xff0c;该示例项目前端xaml使用UI库 &#xff0c;Material Design Themes UI 来构建用户界面&#xff0c;确保…

【into outfile写文件】

简介 select * from user into outfile C:/Users/ichunqiu/Desktop/PhpStudy2018/PHPTutorial/WWW/1.txt;用法的意思就是把user表中查询到的所有字段都导出到1.txt文件中 我们之前还有学到dumpfile&#xff0c;单是它只能导出一条数据 写入shell 测试注入点 usernameadmin&…

【工具】使用perf抓取火焰图

背景 当程序存在cpu性能问题时&#xff0c;我们需要找到是哪个函数占用较多的CPU&#xff0c;也就是找出热点函数&#xff1b;perf的火焰图就是这个用途 安装 在Linux系统中&#xff0c;perf 是 Linux 内核提供的性能分析工具&#xff0c;它通常包含在内核源代码包中。大多数…

编码方式知识整理【ASCII、Unicode和UTF-8】

编码方式 一、ASCII编码二、Unicode 编码三、UTF-8编码四、GB2312编码五、GBK编码 计算机中对数据的存储为二进制形式&#xff0c;但采用什么样的编码方式存储&#xff0c;效率更高。主要编码方式有 ASCII、Unicode、UTF-8等。 英文一般为1个字节&#xff0c;汉字一般为3个字节…

Linux 线程互斥

1.相关背景概念 临界资源&#xff1a;多线程执行流共享的资源就叫做临界资源 临界区&#xff1a;每个线程内部&#xff0c;访问临界资源的代码&#xff0c;就叫做临界区 #include <iostream> #include <pthread.h> #include <string> #include <vector…

mac安装jdk8

这里写自定义目录标题 一、下载JDK8二、安装JDK8三、配置环境变量3.1 找到JDK安装目录3.2 打开终端&#xff1a;3.3 输入如下配置&#xff1a;3.3 查看配置是否成功&#xff1a; 一、下载JDK8 oracle官网下载或从下面链接获取 https://download.csdn.net/download/qq_44107684…

【小沐学Golang】基于Go语言搭建静态文件服务器

文章目录 1、简介2、安装2.1 安装版2.2 压缩版 3、基本操作3.1 go run3.2 go build3.3 go install3.4 go env3.5 go module 4、文件服务器4.1 filebrowser4.2 gohttpserver4.3 goFile 5、FAQ5.1 go.mod 为空5.2 超时 结语 1、简介 https://golang.google.cn/ Go语言诞生于2007…

day02 -- docker

1.docker的介绍 Docker 是一个开源的应用容器引擎&#xff0c;基于 Go语言 并遵从 Apache2.0 协议开源。Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。容器是完全使…

●day 35 动态规划part01

第九章 动态规划part01 动态规划的类别 理论基础 动态规划下五步曲&#xff1a; 1、确定dp数组&#xff08;dp table&#xff09;以及下标的含义 2、确定递推公式 3、dp数组如何初始化 4、确定遍历顺序 5、打印dp数组 代码随想录 斐波那契数 代码随想录 动态规划5部曲 cla…

高级语言源程序转换为可执行目标文件

将高级语言源程序转换为可执行目标文件的过程通常包括以下几个主要步骤&#xff1a; ​ 1. 预处理&#xff08;Preprocessing&#xff09;&#xff1a; 由谁完成预处理器&#xff08;cpp&#xff09;操作处理源代码中的预处理指令&#xff08;如宏定义、文件包含、条件编译等&…

Linux——动态卷的管理

确保已经设置了对应的动态卷的驱动&#xff08;provisioner 制备器&#xff09;基于动态驱动创建对应的存储类创建PVC &#xff08;PVC 将会自动根据大小、访问模式等创建PV&#xff09;Pod的spec 中通过volumes 和 volumemounts 来完成pvc 的绑定和pvc对应pv的挂载删除pod 不…

Linux网络编程(七)-TCP协议客户端及代码实现

1.TCP的客户端代码流程简述 这一章将为大家讲解Socket通信中客户端的实现过程&#xff0c;还是先上图&#xff0c;请大家了解客户端的步骤 可以看到&#xff0c;相比服务端&#xff0c;客户端的步骤简单的很多。事实上这种情况比较多&#xff0c;比如一个服务端会有多个客户端…

JMeter模拟并发请求

PostMan不是严格意义上的并发请求工具&#xff0c;实际是串行的&#xff0c;如果需要测试后台接口并发时程序的准确性&#xff0c;建议采用JMeter工具。 案例&#xff1a;JMeter设置20个并发卖票请求&#xff0c;查看后台是否存在超卖的情况 方式一&#xff1a;一共10张票&…

TrickMo 安卓银行木马新变种利用虚假锁屏窃取密码

近期&#xff0c;研究人员在野外发现了 TrickMo Android 银行木马的 40 个新变种&#xff0c;它们与 16 个下载器和 22 个不同的命令和控制&#xff08;C2&#xff09;基础设施相关联&#xff0c;具有旨在窃取 Android 密码的新功能。 Zimperium 和 Cleafy 均报道了此消息。 …

编写一个通用的i2c控制器驱动框架

往期内容 I2C子系统专栏&#xff1a; I2C&#xff08;IIC&#xff09;协议讲解-CSDN博客SMBus 协议详解-CSDN博客I2C相关结构体讲解:i2c_adapter、i2c_algorithm、i2c_msg-CSDN博客内核提供的通用I2C设备驱动I2c-dev.c分析&#xff1a;注册篇内核提供的通用I2C设备驱动I2C-dev.…

时空数据时序预测模型: HA、VAR、GBRT、GCN、DCRNN、FCCF、ST-MGCN

HA (Historical Average) HA (Historical Average&#xff0c;历史平均模型) 是一种基础的时间序列预测方法&#xff0c;通常用于预测具有周期性或季节性规律的数据。它通过计算历史上同一时间段的平均值来预测未来值&#xff0c;假设数据会遵循某种周期性的变化模式。以下是对…

智能家居的“眼睛”:计算机视觉如何让家更智能

引言 在不远的未来&#xff0c;当我们走进家门&#xff0c;灯光自动亮起&#xff0c;空调已经调至最舒适的温度&#xff0c;甚至音乐也播放着我们最喜欢的歌曲。 这一切&#xff0c;都得益于智能家居系统的发展。而在这个系统中&#xff0c;计算机视觉技术扮演着至关重要的角色…