哈希一刀流——布隆过滤器详解

article/2025/11/8 1:36:48

目录

    • 传统艺能😎
    • 背景🤔
    • 概念🤔
    • 误判控制😋
    • 具体实现😋
    • 插入😋
    • 查找😋
    • 删除😋
    • 优劣😋
    • 使用场景😋

传统艺能😎

小编是双非本科大二菜鸟不赘述,欢迎米娜桑来指点江山哦

🎉🎉非科班转码社区诚邀您入驻🎉🎉
小伙伴们,打码路上一路向北,彼岸之前皆是疾苦
一个人的单打独斗不如一群人的砥砺前行
这是和梦想合伙人组建的社区,诚邀各位有志之士的加入!!
社区用户好文均加精(“标兵”文章字数2000+加精,“达人”文章字数1500+加精)
直达: 社区链接点我


在这里插入图片描述

背景🤔

听名字就有种洋玩意儿的高端感,但实际上用的十分广泛,在校招面经里面看到了有大厂在问布隆过滤器,这里我就说一下我的拙见:

在上手新游戏的时候,你想到一个很有逼格的昵称,但此时系统告诉你 “此昵称已被注册”,这个昵称的唯一性就是运用了哈希的布隆过滤器,他本质上是就是一个 key 的模型,他只需要判断对象是否存在过就行。

按照我之前的思路,这种问题就有两种方法:

方法一:用红黑树或哈希表将所有使用过的昵称存储起来,直接判断该昵称是否在红黑树或哈希表中即可,但红黑树和哈希表最大的问题就是浪费空间,当昵称数量非常多的时候内存当中根本无法 hold 住
方法二:虽然位图只能存储整型数据,但我们可以通过一些哈希算法将字符串转换成整型,比如BKDR哈希算法,我们只需要直接判断位图中该昵称对应的比特位是否被设置即可

虽然但是,使用位图的情况下,256 个字符串排列组合有无限种可能,而映射成 10 个数字的单元上,不论用何种哈希算法都是必定会出现大量冲突的,硬要使用的话,还会面临一个更大的问题:因为不同的字符串被映射成了相同的整型,此时就会出现 误判现象 \color{red} {误判现象} 误判现象,明明没有使用过的昵称也会被判定为已使用,于是 布隆过滤器 \color{red} {布隆过滤器} 布隆过滤器横空出世

概念🤔

布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的一种紧凑的、比较巧妙的概率型数据结构,特点是高效地插入和查询

布隆过滤器其实就是位图的一个变形和延申,虽然无法避免哈希冲突,但我们可以想办法降低误判的概率;当一个数据映射到位图中时,布隆过滤器会用多个哈希函数映射到多个比特位,当判断一个数据是否在位图当中时,需要分别根据这些哈希函数计算出对应的比特位,比特位设置了代表着当前状态的默认值,设置为 1 则判定为该数据存在,这一点很类似于我们定义红黑数的节点颜色。

布隆过滤器使用多个哈希函数进行映射,目的就在于降低哈希冲突的概率,一个哈希函数产生冲突的概率可能比较大,但多个哈希函数同时产生冲突的概率可就没那么大了!

举个栗子:假设此时用三个哈希函数进行映射,那么 “张三” 这个昵称被使用后位图中会有三个地方会被置 1,当有人要使用 “李四” 这个昵称时,就算前两个哈希函数计算出来的位置都产生了冲突,但由于第三个哈希函数计算出的比特位的值为 0,此时系统就会判定“李四”这个昵称没有被使用过:
在这里插入图片描述
但是尽管如此,只要数据基数足够大,也会出现误判情况:比如此时又有人要使用 “王五” 这个昵称,虽然 “王五” 计算出来的三个位置既不和“张三”完全一样,也不和“李四”完全一样,但“王五”的三个位置都被“张三”和“李四”占用了,此时系统也会误判 “王五” 这个昵称已经被使用过了:
在这里插入图片描述
😋😋**敲黑板!**😋😋

虽然布隆过滤器判断一个数据存在可能是不准确的,因为这个数据对应的比特位可能被其他一个数据或多个数据占用了;但是布隆过滤器判断一个数据不存在却是准确的,因为如果该数据存在那么该数据对应的比特位都应该已经被设置为1了

误判控制😋

很显然,过小的布隆过滤器比特位很快就会都被设为 1,此时误判率就会飙升,因此布隆过滤器的长度会直接影响误判率,布隆过滤器的长度越长其误判率越小

此外,哈希函数的个数也需要权衡,哈希函数的个数越多布隆过滤器中比特位被设置为1的速度越快,但是布隆过滤器的效率越低,但如果哈希函数的个数太少,也会导致误判率变高

大佬在权衡过其中的关系后得出了一套比较得当的公式:
在这里插入图片描述

k 是哈希函数个数
m 为布隆过滤器长度
n为插入的元素个数
p为误判率。

我们这里可以大概估算一下,如果使用 3 个哈希函数,那么 k 的值就为 3,ln2 的值我们取 0.7,那么 m 和 n 的关系大概是 m = 4 × n ,也就是过滤器长度应该是插入元素个数的 4 倍

具体实现😋

因为插入过滤器的元素不仅是字符串,也可以是其他类型的数据,只有调用者能够提供对应的哈希函数将该类型的数据转换成整型即可,但一般情况下过滤器都是用来处理字符串的,我们布隆过滤器可以实现为一个模板类,所以这里可以将模板参数 K 的缺省类型设置为 string

布隆过滤器中的成员一般也就是一个位图,我们可以在布隆过滤器这里设置一个非类型模板参数 N,用于指定位图的长度:

template<size_t N, class K = string, class Hash1 = BKDRHash, class Hash2 = APHash, class Hash3 = DJBHash>
class BloomFilter
{
public://...
private:bitset<N> _bs;
};

实例化布隆过滤器需要调用者提供三个哈希函数,由于布隆过滤器一般处理的是字符串类型的数据,因此这里我们可以默认提供几个将字符串转换成整型的哈希函数。

这里选取将字符串转换成整型的哈希函数,是综合评分最高的 BKDRHashAPHashDJBHash,这三种哈希算法在多种场景下产生哈希冲突的概率是最小的:

struct BKDRHash
{size_t operator()(const string& s){size_t value = 0;for (auto ch : s){value = value * 131 + ch;}return value;}
};
struct APHash
{size_t operator()(const string& s){size_t value = 0;for (size_t i = 0; i < s.size(); i++){if ((i & 1) == 0)//偶数情况{value ^= ((value << 7) ^ s[i] ^ (value >> 3));}else//奇数情况{value ^= (~((value << 11) ^ s[i] ^ (value >> 5)));}}return value;}
};
struct DJBHash
{size_t operator()(const string& s){if (s.empty())return 0;size_t value = 5381;for (auto ch : s){value += (value << 5) + ch;}return value;}
};

插入😋

布隆过滤器当中需要提供一个 set 接口用于插入元素,插入元素时,需要通过三个哈希函数分别计算出该元素对应的三个比特位,然后在位图中设置为1即可:

void Set(const K& key)
{size_t i1 = Hash1()(key) % N;size_t i2 = Hash2()(key) % N;size_t i3 = Hash3()(key) % N;//设置位图中三个位状态_bs.set(i1);_bs.set(i2);_bs.set(i3);
}

查找😋

布隆过滤器当中还需要提供一个 test 接口用于检测元素是否已经出现。检测时,需要通过三个哈希函数分别计算出该元素对应的三个比特位,然后判断这三个比特位是否被设置为1。

  1. 只要有一个比特位未被设置则说明该元素一定不存在。
  2. 如果三个比特位全部被设置,则返回 true 表示该元素存在(可能仍存在误判)
bool Test(const K& key)
{size_t i1 = Hash1()(key) % N;if (_bs.test(i1) == false){return false; //key一定不存在}size_t i2 = Hash2()(key) % N;if (_bs.test(i2) == false){return false; //key一定不存在}size_t i3 = Hash3()(key) % N;if (_bs.test(i3) == false){return false; //key一定不存在}return true; //key对应的三个位都被设置,key存在(可能误判)
}

删除😋

布隆过滤器一般不支持删除操作,原因如下:

首先布隆过滤器判断一个元素存在时可能存在误判,此时无法保证要删除的元素确实在过滤器当中,此时将位图中对应的比特位清 0 会影响其他元素

此外,就算要删除的元素确实在布隆过滤器当中,该元素映射的多个比特位当中有些是与其他元素共用的,此时将这些比特位清 0 也会影响其他元素

当然并不是说没办法让他正常删除,结合上面我们需要两种做法

  1. 调用 test 接口确认此对象在过滤器中可能存在,再进一步遍历存储对象的文件来确认是否真正存储有;
  2. 将删除操作进行解耦,保证不会影响其他的元素,我们只需要在每个比特位加一个计数器,当存在插入操作时,在计数器里面进行 ++ 操作,删除后对该位置进行 – 即可。

当然, 为什么至今过滤器都没有提供对应的删除接口呢? \color{red} {为什么至今过滤器都没有提供对应的删除接口呢?} 为什么至今过滤器都没有提供对应的删除接口呢?其实过滤器的本来目的就是为了提高效率和节省空间,但是在确认存在时去遍历文件,文件 IO 和磁盘 IO 的时间开销是不小的,其次在每个比特位增加额外的计数器,更是让空间开销飙升到本身的好几倍。

优劣😋

虽然莫须有过滤器只能保证对象可能存在,我愿称之为薛定谔的过滤器,但是他的优势也绝对亮眼:

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

当然缺点也不能没有:

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

使用场景😋

因为是判断可能存在,所以首要标准就是不能到影响业务逻辑

比如当我们用手机号注册账号时,系统需要判断你填入的手机号是否已经注册过,如果注册过则会提示用户注册失败,但这种情况系统不可能直接去遍历磁盘当中的用户数据,判断该手机号是否被注册过,因为磁盘IO是很慢的,这会降低用户的体验。

这种情况下就可以使用布隆过滤器,将所有注册过的手机号全部添加到布隆过滤器当中,当我们需要用手机号注册账号时,就可以直接去布隆过滤器当中进行查找:如果不存在,则说明没有被注册过,并且避免了磁盘IO。如果存在,此时再进一步访问磁盘进行复核,确认该手机号是否真的被注册过即可

大部分情况下用户用手机号注册账号时,都知道自己有没有注册过,因此在布隆过滤器就能直接搞定,此时就避免了进行磁盘IO。而只有布隆过滤器误判或用户忘记的情况下,才需要访问磁盘进行复核

aqa 芭蕾 eqe 亏内,代表着开心代表着快乐,ok 了家人们


http://chatgpt.dhexx.cn/article/WRicTbhx.shtml

相关文章

连米哈游都成了第二,这个一刀999的页游大王把老外吃透了。

自从《 原神 》在 2020 年上线之后&#xff0c;米哈游基本就稳坐出海收入第一的王座。 但是&#xff0c;稳坐榜首的米哈游&#xff0c;却在今年 10 月&#xff0c;被突然冒出的三七互娱一脚踢了下来。 没错&#xff0c;就是那个做出了《 大天使之剑 》、《 传奇霸业 》的页游大…

如何产生一个全局唯一的流水号(附demo)

本文介绍如何使用最简单的方法产生一个全局唯一的流水号&#xff0c;支持集群&#xff0c;性能可靠&#xff0c;并且经过实际的应用 唯一流水号的格式为当前系统时间当前服务器编号并发序列号&#xff0c;长度最短可为17位&#xff0c;每毫秒支持生成多个并且支持集群部署 废话…

记小辉人生中的第一刀

本篇是小辉趴在床上写完的博客&#xff0c;讲的是半个月前“肛裂肛瘘混合痔”手术到术后两周的康复过程以及一些注意事项和提示&#xff0c;给读者们的健康提个醒&#xff0c;欢迎转发给身边正在经历痛苦或者犹豫的朋友。 希望大家都身体健康&#xff0c;永远用不上我下面提到的…

砍一刀免费拿营销玩法解说

大家应该都收到拼多多的砍价链接&#xff0c;小来对拼多多这种扰民是推广方式非常反感&#xff0c;但不得不承认&#xff0c;这个营销方式却是拼多多最有效的营销方式之一。 拼多多最新的财报显示&#xff0c;拼多多2020年活跃买家数为7.884亿&#xff0c;首度超越阿里位居全国…

【leetCode:剑指 Offer】06. 从尾到头打印链表

1.题目描述 输入一个链表的头节点&#xff0c;从尾到头反过来返回每个节点的值&#xff08;用数组返回&#xff09;。 2.算法分析 倒序输出&#xff0c;使用栈Stack的数据结构。 先将链表中的元素入栈&#xff0c;然后遍历栈内元素&#xff0c;将元素加入到数组中。 3.代码实现…

流水

这几天怎么过去的 在纸上画一笔 就这么轻松 没和任何人联系 似乎很忙 总是在上课上课上课 从这个教室到那个教室 从这个实验室到那个实验室 嗜睡得厉害 心里很空 很久没有S的消息 有点冷

R语言--readr包读写数据

文章目录 前言一、发现问题二、分析问题三、解决问题四、读写速度总结吐槽 前言 当你使出了浑身解数&#xff0c;read.csv和read.table还是无法读入数据时&#xff0c;或许可以尝试一下readr包中的read_table&#xff0c;read_csv等函数。尝试一下&#xff01; 我是一个很专一…

R语言入门:读取csv文件及获取统计数值(1)

本次编程尝试使用R语言读取csv文件“filesize.csv”&#xff0c;并根据内容绘制统计图和得出平均数&#xff0c;中位数和方差等简单数据。 csv文件内容1001个数值至少大于1000的小数组成&#xff0c;每行一个&#xff0c;共计1001行 在打开文件之前&#xff0c;我们可能会遇到…

R语言 读取文件

1. R读取txt文件 使用R读取txt文件直接使用read.table()方法进行读取即可&#xff0c;不需要加载额外的包。 read.table("/home/slave/test.txt",headerT,na.strings c("NA"))注意&#xff0c;此处的na.strings c("NA") 的意思是文件中的缺失数…

R从文件中读取数据,输出文件

看了几天的书&#xff0c;终于到这一步了&#xff0c;说实话&#xff0c;用R来做统计&#xff0c;很少有人手动的去输入那些数字&#xff0c;肯定是从别的地方导入的&#xff0c;我们用来处理就可以了&#xff0c;所以到这里才算是真正的入门&#xff0c;前面都是做基础的练手。…

R语言中文件或数据的读写

文章目录 系统数据的读写xlsx文件的读写举例说明具体代码结果如下 csv文件的读写读取csv文件写入csv文件使用readr包读取csv文件使用data.table包读取csv文件 scan函数scan()函数的使用语法参数含义描述实例&#xff1a; 系统数据的读写 save(a,b,file"test.RData")…

掌握R语言文件读取方法

目标 掌握 R语言文件读取方法 学习笔记 utils包内Date Input用法base包内readLines用法stringi包内stri_read_linesxlsx包内Date Input用法readr包内 Read a delimited file 用法 1.utils包内Date Input用法 以read.table为例。 read.table参数详细说明见http://www.360doc…

R语言入门(15)_读取文件(read)

目录 一、read.table() 读取工作路径下的纯文本文件&#xff08;.txt&#xff09;(.csv) 1、工作路径的设置 2、head函数——只显示数据前几行 3、read.table()的其他一些参数 二、与read.table相类似的函数 三、read.table()读取网络上的文本文件 三、读取非文本文件 …

使用R读取并查看数据

本篇文章介绍如何使用R读取并查看数据&#xff0c;包含一些最基础的函数使用方法和说明。后面还会陆续介绍数据清洗&#xff0c;匹配和提取等相关的操作。 查看函数帮助 对于新手来说&#xff0c;在使用R时最重要的是了解不同函数的使用方法。很多时候我们都是边用边学的状态&…

R语言如何从外部读取数据到R中

R语言可以从键盘&#xff0c;文本&#xff0c;excel&#xff0c;access&#xff0c;数据库&#xff0c;专业处理软件sas 一、使用键盘的输入 mydata<-data.frame(agenumeric(0),gendercharacter(0),weightnumeric(0)) mydata<-edit(mydata) 二、读入带有分隔符文本格式…

R语言入门——数据快速读取与查看(含实例代码和参数讲解)

R语言数据读取 介绍引言结构安排 数据读取函数文本数据readLines函数 键盘键入数据scan函数讲解 表格数据.xlsx文件介绍表格数据函数参数介绍 快速读入参数介绍竞赛数据练习2020美赛C题2019华为杯E题 数据查看函数表 介绍 引言 毕业季来临&#xff0c;很多小伙伴都在积极准备…

R语言读CSV、txt文件方式以及read.table read.csv 和readr(大数据读取包)

首先准备测试数据*(mtcars)分别为CSV. TXT **2018博客之星评选&#xff0c;如果喜欢我的文章&#xff0c;请投我一票&#xff0c;编号&#xff1a;No.009** [支持连接](https://blog.csdn.net/HHTNAN/article/details/85330758) ,万分感谢&#xff01;&#xff01;&#x…

R语言之读取文件夹的数据

读取文件路径&#xff1a;一层目录&#xff08;“示例”&#xff09;、二层目录&#xff08;“数据1”、“数据2”&#xff09;下的表格数据。 “示例”文件下&#xff1a; “数据1”文件下&#xff1a; “数据2”文件下&#xff1a; 读取文件夹 rm(listls()) #清除变量 …

R入门(一)----读取数据、查看数据

感谢大鹏dapengde 创建数据 #在相应位置新建文件夹 dir.create(E:/R/R lab/学R/r4r) #将数据文件存进文件夹 write.csv(as.data.frame(t(matrix(co2,12,dimnames list(month.abb,unique(floor(time(co2))) ) ))),file E:/R/R lab/学R/r4r/co2.csv)读取数据 R数据的导入与…

手把手教你用R语言读取CSV文件

导读&#xff1a;R语言有许多种方法去获取数据&#xff0c;最常用的是读取CSV文件。 作者&#xff1a;Jared P. Lander 来源&#xff1a;大数据DT&#xff08;ID&#xff1a;hzdashuju&#xff09; 读取CSV文件最好的方法是使用read.table函数&#xff0c;许多人喜欢使用read.c…