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

news2024/10/23 19:11:23

前言

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

一、什么是布隆过滤器

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

【自信的我开始吟唱】:

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

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

布隆过滤器原理图

二、实现原理

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

【无比自信的我】:

  • 底层原理是通过一个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.coloradmin.cn/o/2221800.html

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

相关文章

一、python基础

python基础 认识Python1. Python介绍1.1 为什么学习Python1.2 Python发展历史 2. 语言分类简介2.1 编译型2.2 解释型 Python环境搭建1. Python 解释器1.1 Python解释器下载1.2 Python解释器安装 2. 解释器运行Python脚本2.1 演练步骤 PyCharm1. PyCharm介绍2. PyCharm安装3. Py…

15分钟学Go 第6天:变量与常量

第6天&#xff1a;变量与常量 在Go语言中&#xff0c;变量和常量是编程的基础概念。理解如何定义和使用它们不仅能帮助我们管理数据&#xff0c;还能增强代码的可读性和可维护性。在本章中&#xff0c;我们将详细探讨Go语言中的变量和常量&#xff0c;涵盖它们的定义、使用、作…

机器学习建模分析

机器学习 5.1 机器学习概述5.1.1 机器学习与人工智能5.1.2 python机器学习方法库 5.2 回归分析5.2.1 回归分析原理5.2.2 回归分析实现 5.3 分类分析5.3.1 分类学习原理5.3.2 决策树5.5.3 支持向量机 5.4 聚类分析5.4.1 聚类任务5.4.2 K-means算法 5.5 神经网络和深度学习5.5.1神…

python配合yolov11开发分类训练软件

上一篇文件写了用yolo分类模型开发分类软件&#xff0c;这边文章在上个分类软件的基础上加入训练功能环境配置:pycharm&#xff0c;PySide6 6.6.1 &#xff0c;PySide6-Addons 6.6.1&#xff0c;PySide6-Essentials 6.6.1&#xff0c;torch 2.3.1cu121&#xff0c;torchaudio 2…

dynadot设置域名动态DNS(DDNS)

需求&#xff1a;本地测试代理&#xff0c;代理需要绑定IP或者域名&#xff0c;本地IP是动态变化的&#xff0c;解决办法就是给域名设置动态DNS 1.dynadot设置 开启动态DNS选项会显示动态DNS密码&#xff0c;该密码后续将会用在DDNS-GO工具上 2.DDNS-GO设置 GitHub介绍页面&a…

WIFI、NBIOT、4G模块调试AT指令连接华为云物联网服务器(MQTT协议)

一、前言 随着物联网&#xff08;IoT&#xff09;技术的飞速发展&#xff0c;越来越多的设备开始连接到互联网&#xff0c;形成了一个万物互联的世界。在这个背景下&#xff0c;设备与云端之间的通讯变得尤为重要。 本文将探讨几种常见的无线通信模块——EC20-4G、Air724ug-4…

每天花2分钟学数字化转型,第四讲:数字化转型

一文看懂&#xff1a;数字化转型是什么&#xff1f;以及数字化转型的根本任务与核心路径。 定义&#xff1a;数字化是人类社会的进化&#xff0c;绝不仅仅是一个企业的问题&#xff0c;也不是某一项技术的问题&#xff0c;而是时代的变迁。数字化转型指的是从当前信息化环境下…

RabbitMQ系列学习笔记(三)--工作队列模式

文章目录 一、工作队列模式原理二、工作队列模式实战1、抽取工具类2、消费者代码3、生产者代码4、查看运行结果 本文参考 尚硅谷RabbitMQ教程丨快速掌握MQ消息中间件rabbitmq RabbitMQ 详解 Centos7环境安装Erlang、RabbitMQ详细过程(配图) 一、工作队列模式原理 与简单模式相…

企业级 接口自动化测试框架:Pytest+Allure+Excel

1. Allure 简介 简介 Allure 框架是一个灵活的、轻量级的、支持多语言的测试报告工具&#xff0c;它不仅以 Web 的方式展示了简介的测试结果&#xff0c;而且允许参与开发过程的每个人可以从日常执行的测试中&#xff0c;最大限度地提取有用信息。 Allure 是由 Java 语言开发…

MySQL 【日期】函数大全(七)

目录 1、UNIX_TIMESTAMP() 将指定的日期/日期时间转为 UNIX 时间戳值。 2、WEEK() 返回给定日期位于当年的第几周。 3、WEEKDAY() 返回给定日期的工作日编号。 4、WEEKOFYEAR() 返回给定日期位于当年的第几周 5、YEAR() 提取日期的年份部分并作为数字返回。 6、YEARWEEK()…

Jmeter 实战 JDBC配置

​ JDBC JDBC&#xff08;Java Database Connectivity&#xff09;是一种用于执行SQL语句的Java API。通过这个API&#xff0c;可以直接连接并执行SQL脚本&#xff0c;与数据库进行交互。 使用JMeter压力测试时&#xff0c;操作数据库的场景 在使用JMeter进行接口压力测试时…

Gin 协程mysql客户端

一、Gin框架 mysql配置 这里选择yaml文件配置 二、配置读取 viper 读取yaml文件中对应配置 三、mysql 的协程客户端 文件位置 package databaseimport ("database/sql""fmt""github.com/spf13/viper""log""net/http"&quo…

JavaWeb 25.Vite

目录 一、Vite的介绍 二、Vite创建Vue3工程化项目 ViteVue3项目的创建、启动、停止 创建 启动 停止 干净感来源于对自我的驯服 —— 24.10.23 一、Vite的介绍 在浏览器支持 ES 模块之前&#xff0c;JavaScript 并没有提供原生机制让开发者以模块化的方式进行开发。这也正是我们…

Missing classes detected while running R8报错解决方案

Android 打包release版本时报错如下&#xff1a; > Task :printlib:minifyReleaseWithR8 FAILED AGPBI: {"kind":"error","text":"Missing classes detected while running R8. Please add the missing classes or apply additional ke…

canvas-editor首行缩进

canvas-editor中渲染部分的源码都在Draw.ts里&#xff0c;能找到computeRowList方法中并没有实现首行缩进相关的逻辑&#xff0c;但是实现了element.type ElementType.TAB的缩进&#xff0c;如图&#xff1a; 因此我们可以基于tab进行首行缩进的逻辑编写&#xff0c;在main.ts…

通过DevTools逃离Chrome沙盒(CVE-2024-6778和CVE-2024-5836)

介绍 这篇博文详细介绍了如何发现CVE-2024-6778和CVE-2024-5836的&#xff0c;这是Chromium web浏览器中的漏洞&#xff0c;允许从浏览器扩展&#xff08;带有一点点用户交互&#xff09;中进行沙盒逃逸。 简而言之&#xff0c;这些漏洞允许恶意的Chrome扩展在你的电脑上运行…

2015年-2017年 计算机技术专业 程序设计题(算法题)实战_c语言程序设计数据结构程序设计分析

文章目录 20151.C语言算法设计部分2.数据结构算法设计部分 20161.C语言算法设计部分2.数据结构算法设计部分 2017年1. C语言算法设计部分2.数据结构算法设计部分 2015 1.C语言算法设计部分 int total(int n) {if(n1) return 1;return total(n-1)n1; } //主函数测试代码已省略…

Android 15 推出新安全功能以保护敏感数据

Android 15 带来了增强的安全功能&#xff0c;可保护您的敏感健康、财务和个人数据免遭盗窃和欺诈。 它还为大屏幕设备带来了生产力改进&#xff0c;并对相机、消息和密钥等应用进行了更新。 Android 防盗保护 Google 开发并严格测试了一套全面的功能&#xff0c;以在盗窃之…

Ubuntu22.04 制作系统ISO镜像

第一步&#xff1a;安装软件-Systemback 1.如果已经添加过ppa&#xff0c;可以删除重新添加或者跳过此步 sudo add-apt-repository --remove ppa:nemh/systemback 2.添加ppa 我是ubuntu20&#xff0c;但这个软件最后支持的是 ubuntu16.04版本&#xff0c;所以加一个16版本…

【Docker】Harbor 私有仓库和管理

目录 一、搭建本地私有仓库 二、harbor简介&#xff08;特性、构成、架构的数据流向&#xff09; 2.1 什么是Harbor 2.2 Harbor的特性 2.3 Harbor的构成 2.4 Harbor的工作原理&#xff08;运行流程&#xff09; 三、harbor部署以及配置文件 1. 部署 Docker-Compose 服…