原来这就是 布隆过滤器

news2024/12/23 20:43:53

1.布隆过滤器的引出

一个有趣的现象

不知道大家有没有发现这么一个现象,当我们在使用一些软件的时候,比如像 CSDN、这种具有推荐算法的应用,他并不会给我们推送我们已经浏览过的内容,这是怎么做到的呢?

说白了就是人家的程序中实现了一个去重算法。大致的原理就是,在服务器的内部记录用户的浏览记录,每次推送新内容的时候,去用户的浏览记录中进行查找,看有没有重复的内容,有的话就pass掉,这样一来,我们就不会收到重复的内容了。

在实际应用中,查找的数据往往是字符串类型的,所以,高效查找是否有重复的字符串内容就显得尤为重要,问题来了,如何做到高效的查询字符串是否存在呢?

如何快速查找非整形的数据

如果你数据结构学得不错的话,你可能已经想到一些适合查找的数据结构和算法,比如:二分查找算法、二叉搜索树、哈希表。是的,这些数据结构的查询效率确实不错,尤其是哈希表,能到达O(1) 的时间复杂度,但是,这些数据结构存储的是元素本身,当数据量比较大的时候,消耗的内存资源是非常恐怖的,因此,我们可以参考位图的思想,用比特位来标记元素是否存在,从而降低数据存储所要耗费的内存空间。(如果你不了解位图的话,推荐阅读这篇文章,这篇文章中对位图做了详细讲解——文章链接)

但是,又有新问题了,位图是有缺陷的,位图只能处理整形的数据,而我们要处理的数据可不只是整形的;没事,我们可以通过字符串哈希算法,将字符串映射为整形数据,再将整形数据通过哈希函数和存储位置建立一 一 映射的关系,这样一来就可以进行查找喽。等等,我怎么感觉这就是一个位图呀?没错,上面讲的就是利用位图来解决问题的,但是位图能完全解决判断字符串是否存在的问题吗?

举个例子:用户注册的时候,假如孙悟空这个数据存在,映射的bit位为1,没毛病;但是,如果齐天大圣这个数据不存在,却和孙悟空映射到了同一个bit位,那么就会造成误判。

如下图所示:

当我们使用某种字符串哈希算法的时候,将字符串映射为整形数据时,我们并不能保证,不同的数据就一定映射到不同的位置上,哈希算法是会产生哈希冲突的。而且字符串类型的数据充满不确定性,映射到一个bit位上,产生冲突的概率是比较大的;

这个时候,有一位名叫布隆的大佬就想到了一个办法,既然映射一个比特位容易冲突,那我就多映射几个比特位,并且映射每个bit位的哈希算法不一样,当我们想要判断一个数据是否存在,需要判断该数据所映射的每个bit位,这个时候就大大减小了冲突的概率。 这就是实现布隆过滤器的思想。

比如:还是用户注册的场景,孙悟空映射了三个bit位,并且三个bit位都为1,所以孙悟空是存在的,但是齐天大圣映射的bit位中,有一个bit位不为1,所以齐天大圣是不存在的。

2.布隆过滤器的介绍

什么是布隆过滤器

通过上面的介绍可以看出,布隆过滤器是通过位图和哈希思想实现的数据结构,可以进行高效的查询操作。

  • 通过使用位图,大大降低了存储数据所需要的空间。
  • 通过使用哈希思想,大大提高了查询的效率。

可以看出,布隆过滤器是一种兼顾时间复杂度和空间复杂度的数据结构。但是布隆过滤器也有不完美的地方,因为布隆过滤器其实是存在误判的

比如孙悟空和齐天大圣是已经注册过的了,并且二者映射的bit位都位1,如果猪八戒不存在,但是其映射的bit位被孙悟空和齐天大圣映射过了,也都为1,所以判断猪八戒是否存在的时候,就存在误判。误判的概率有大有小,所以布隆过滤器还是一种概率型的数据结构。

布隆过滤器的特点

  • 判断数据不存在,没有毛病,判断的很准确。
  • 判断数据存在,可能会出现误判。

所以布隆过滤器的使用场景是要能够容忍误判的;如果说误判总是可能会存在,那我们就需要通过一定的手段控制误判率。

控制布隆过滤器的误判率

1.控制布隆过滤器的长度。

  • 如果布隆过滤器的长度比较短,布隆过滤器就很容易被填满,当布隆过滤器接近填满状态的时候,出现误判的可能性就非常大。所以布隆过滤器的长度越长,误判的可能性就越小。

2.控制哈希函数的个数。

  • 如果哈希函数比较多的话,一个数据就要映射更多的bit位,布隆过滤器的效率难以保证;如果哈希函数比较少的话,误判的可能性又会变大。

3.控制元素个数。

  • 如果元素个数比较多,映射的bit位接近布隆过滤器的长度,此时的误判率较大,所以数据元素的个数越少,误判率越低。

于是,有人研究了这个问题,并得出了以下结论:

其中,K位哈希函数的个数,m为布隆过滤器的长度,n为插入的元素个数,p为误判率。当k、m、n 满足上述公式时,误判率较低。假设k位3,我们可以计算出,布隆过滤器的长度大约等于数据个数的四倍左右,此时的误判率较低。

3.布隆过滤器的使用场景

前面简单的提了一下,布隆过滤器是会发生误判的,所以想要使用布隆过滤器,需要忍受误判,如果不想容忍误判,就需要作出相应的措施了。

容忍误判的场景

比如:还是用我们注册的例子,当用户提交输入的用户名时,程序可以去布隆过滤器中查找,如果判断为不存在,那么判断是准确的,直接返回注册成功即可;如果判断为存在,此时可能发生误判,用户提交的数据可能存在,也可能不存在,我们可以当做存在处理,也就是让用户重新输入用户名。这种应用场景下,是可以容忍误判的,因为,即使是发生误判,也就是让用户重新输入一下的事。

不容忍误判的场景

如果我们不想容忍误判,我们可以对判断存在的场景特殊处理。还是用户注册的场景;如果判断不存在,判断是准确的,将用户提交的用户名添加进数据库中;如果判断是存在,可能发生误判,此时,我们不想容忍误判,我们可以再去数据库中进行查找,将查找的结果进行返回。

可以看出,布隆过滤器在用户和数据库之间作为一个检查官,当这个检察官能够判断准确的事情,自己就做主返回了,不能判断准确的事情,还得请教一下数据库大哥,大哥说了算。

同时,我们还可以看出,布隆过滤器是一种提高用户访问速度的策略,不存在误判的时候,不需要访问数据库,减少了很多访问数据库的操作,提高了访问速度。存在误判的时候,在去访问数据库,因为,布隆过滤器的查询效率很高,对比于访问数据库的操作微乎其微,因此,使用布隆过滤器是一种稳赚不赔的 “买卖”。

4.布隆过滤器的实现

布隆过滤器中的成员总览

template<size_t N, class K = std::string, class Hash1 = HashFuncBKDR, 
	     class Hash2 = HashFuncAP, class Hash3 = HashFuncDJB>
class BloomFilter
{
public:
	void Set(const K& key)  // 通过几个不同的哈希函数映射出 几个不同的存储位置,减少冲突的概率
	{}
	bool Test(const K& key) // 布隆过滤器 存在误判,因为冲突可以减少,但不能避免
	{}
private:
	static const size_t M = 4 * N;
	std::bitset<M> _bs;
};

模板参数中的介绍

  • N为数据的个数。
  • K位数据的类型,因为,通常用来判断字符串类型的数据,所以我们将缺省值设为string。
  • Hash1、Hash2、Hash3 为三个字符串哈希算法的函数,将字符串数据转换成对应的整形数据。

成员变量的介绍

  • M是布隆过滤器的长度, 当长度的大小为数据个数的4倍左右,误判率较小。
  • _bs是一个位图,布隆过滤器是在位图的基础之上来实现的。

成员方法的介绍

  • Set方法:用于将查询的数据映射的bit位设为1。
  • Test方法:用于判断数据是否存在。

布隆过滤器的插入

布隆过滤器的插入过程是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。布隆所以,我们先要提供几个不同的哈希函数,我们提供以下几个字符串哈希函数

  • HashFuncBKDR
  • HashFuncAP
  • HashFuncDJB 

这几个哈希函数经过测试,产生冲突的概率较小;在代码中以仿函数的形式实现,代码如下:

struct HashFuncBKDR
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto ch : s)
		{
			hash *= 131;
			hash += ch;
		}

		return hash;
	}
};

struct HashFuncAP
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (size_t i = 0; i < s.size(); i++)
		{
			if ((i & 1) == 0) 
			{
				hash ^= ((hash << 7) ^ (s[i]) ^ (hash >> 3));
			}
			else              
			{
				hash ^= (~((hash << 11) ^ (s[i]) ^ (hash >> 5)));
			}
		}

		return hash;
	}
};

struct HashFuncDJB
{
	size_t operator()(const string& s)
	{
		size_t hash = 5381;
		for (auto ch : s)
		{
			hash = hash * 33 ^ ch;
		}

		return hash;
	}
};

经过哈希函数的映射,数据被映射成了一个个的整形数据,但是这些整形数据有可能会超过布隆过滤器的长度,所以我们还需要将哈希值对M取模,得到其在布隆过滤器中的所映射的位置,然后分别调用位图的置一接口,将对应的位置设为1。

插入代码如下:

    void Set(const K& key)  // 通过几个不同的哈希函数映射出 几个不同的存储位置,减少冲突的概率
	{
		size_t hash1 = Hash1()(key) % M;
		size_t hash2 = Hash2()(key) % M;
		size_t hash3 = Hash3()(key) % M;

		_bs.set(hash1);
		_bs.set(hash2);
		_bs.set(hash3);
	}

布隆过滤器的查找

在布隆过滤器中查找一个值,我们只需要判断该数据所映射的bit位是否都为1,只要有一个bit位不为1,就可判断该数据不存在。

注意:经过前面的学习,我们知道,布隆过滤器判断结果为不存在 是准确的,判断结果为存在 是不准确的。

查找代码如下:

    bool Test(const K& key) // 布隆过滤器 存在误判,因为冲突可以减少,但不能避免
	{
		size_t hash1 = Hash1()(key) % M;
		size_t hash2 = Hash2()(key) % M;
		size_t hash3 = Hash3()(key) % M;
        
        // 判断结果为存在时 会有误判
		return _bs.test(hash1) && _bs.test(hash2) && _bs.test(hash3); 
	}

布隆过滤器的删除

在布隆过滤器中,一般不实现删除,假如孙悟空,齐天大圣,猪八戒都存在位图中,但是三者映射的bit位有重复

如果我们此时删除猪八戒,当我们判断孙悟空,齐天大圣时,也会判断不存在。

引用计数实现删除:

如果非要实现删除,我们可以使用引用计数方式实现,将布隆过滤器中的每个比特位扩展成一个小的计数器,一个哈希函数不要只映射一个bit位,而是映射多个bit位,我们这里映射三个bit位,三个bit位表示的数值范围是0~7,因此,每个bit位的引用计数的最大值为7。当不同的数据映射到相同的位置时,我们就可以增加其引用计数,当删除一个数时,我们就可以将其映射位置的引用计数减1。这样一来,删除一个数的时候就不会影响其他值的判断。

但是 这种方式实现的删除是有缺陷的

缺陷一:误判一个数存在,删除的时候,将其映射位置的引用计数错误的减少了。

缺陷二:产生计数回绕问题。计数回绕指的是在使用计数布隆过滤器时,计数器达到其最大可能值后发生溢出(或称为回绕)的情况。这通常发生在计数器的数据类型大小有限的情况下,比如使用了一个固定大小的整数(如intuint32_t)来作为计数器。当计数器的值增加到其最大值(如INT_MAXUINT32_MAX)时,再增加就会回绕到最小值(对于无符号整数是0,对于有符号整数可能是负的最大值)。

布隆过滤器的完整代码

#include <vector>
#include <string>
#include <bitset>
struct HashFuncBKDR
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto ch : s)
		{
			hash *= 131;
			hash += ch;
		}

		return hash;
	}
};

struct HashFuncAP
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (size_t i = 0; i < s.size(); i++)
		{
			if ((i & 1) == 0) 
			{
				hash ^= ((hash << 7) ^ (s[i]) ^ (hash >> 3));
			}
			else              
			{
				hash ^= (~((hash << 11) ^ (s[i]) ^ (hash >> 5)));
			}
		}

		return hash;
	}
};

struct HashFuncDJB
{
	size_t operator()(const string& s)
	{
		size_t hash = 5381;
		for (auto ch : s)
		{
			hash = hash * 33 ^ ch;
		}

		return hash;
	}
};


template<size_t N, class K = std::string, class Hash1 = HashFuncBKDR, 
	     class Hash2 = HashFuncAP, class Hash3 = HashFuncDJB>
class BloomFilter
{
public:
	void Set(const K& key)  // 通过几个不同的哈希函数映射出 几个不同的存储位置,减少冲突的概率
	{
		size_t hash1 = Hash1()(key) % M;
		size_t hash2 = Hash2()(key) % M;
		size_t hash3 = Hash3()(key) % M;

		_bs.set(hash1);
		_bs.set(hash2);
		_bs.set(hash3);
	}
	bool Test(const K& key) // 布隆过滤器 存在误判,因为冲突可以减少,但不能避免
	{
		size_t hash1 = Hash1()(key) % M;
		size_t hash2 = Hash2()(key) % M;
		size_t hash3 = Hash3()(key) % M;

		return _bs.test(hash1) && _bs.test(hash2) && _bs.test(hash3); // 判断结果为存在时 会有误判
	}
	// 布隆过滤器不能实现置0,因为可能会影响其他的值 的存在情况
private:
	static const size_t M = 5 * N;
	std::bitset<M> _bs;
};

5.位图和布隆过滤器对比

布隆过滤器是对位图的一种改进,通过引入多个哈希函数来降低哈希冲突的概率,从而提高查询的准确性和效率。

改进的地方:

  • 多个哈希函数:布隆过滤器使用多个哈希函数将数据映射到位图中的多个位置,而不是像传统位图那样只使用一个哈希函数。这样做的好处是可以降低哈希冲突的概率,从而提高查询的准确性。
  • 误判与空间效率:布隆过滤器允许存在一定的误判率,这是为了换取更高的空间效率。通过调整哈希函数的数量和位图的大小,可以在误判率和空间效率之间找到平衡。这种折衷方案使得布隆过滤器在处理大规模数据集时具有优势。

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

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

相关文章

【物理教学】高中物理速度时间练习

速度时间图像代码 这段代码是一个使用Python编写的脚本&#xff0c;它利用matplotlib库来绘制物理问题中的速度-时间图。代码的主要优点如下&#xff1a; 用户交互&#xff1a;代码通过input函数与用户进行交互&#xff0c;允许用户输入物理问题的参数&#xff0c;如初始速度…

钢结构厂房通风天窗使用场景探讨

钢结构厂房通风天窗作为现代建筑中高效通风的解决方案&#xff0c;广泛应用于多个领域&#xff0c;为各类建筑提供优质的室内环境。成都昱合昇带大家一起探讨通风天窗在不同使用场景下的表现。 1、工业厂房降温 工业厂房是通风天窗典型的应用场景之一。在高温季节或生产过程中产…

苏州科技大学商学院:加强生态保护,推动绿色发展

原标题&#xff1a;苏州科技大学商学院&#xff1a;加强生态保护&#xff0c;推动绿色发展&#xff0c;在美丽中国建设中贡献青春力量 建设美丽中国是全面建设社会主义现代化国家的重要目标&#xff0c;也是激励全国人民为实现中华民族伟大复兴中国梦而共同奋斗的伟大旗帜。中…

CSS3 文本效果(text-shadow,box-shadow,white-space等)文本溢出隐藏并且显示省略号

一 text-shadow text-shadow 属性是 CSS3 中用于为文本添加阴影效果的工具。它可以增强文本的可读性和视觉吸引力&#xff0c;提供丰富的视觉效果 1 语法 text-shadow: offset-x offset-y blur-radius color;offset-x&#xff1a;阴影相对于文本的水平偏移量。可以是正值&am…

STM32CUBEIDE FreeRTOS操作教程(四):timer软件定时器

STM32CUBEIDE FreeRTOS操作教程&#xff08;四&#xff09;&#xff1a;timer软件定时器 STM32CUBE开发环境集成了STM32 HAL库进行FreeRTOS配置和开发的组件&#xff0c;不需要用户自己进行FreeRTOS的移植。这里介绍最简化的用户操作类应用教程。以STM32F401RCT6开发板为例&am…

18047 水仙花数

### 思路 1. 遍历所有的三位数&#xff08;100到999&#xff09;。 2. 对于每个数&#xff0c;提取其百位、十位和个位数字。 3. 计算这些数字的立方和。 4. 如果立方和等于原数&#xff0c;则该数是水仙花数&#xff0c;输出该数。 ### 伪代码 1. 遍历i从100到999&#xff1a…

HTTP中常用的4种请求方式——前端如何发送?后端怎么接受?

一.Get请求&#xff1a; 1.什么是Get请求&#xff1f; 2.前后端如何使用Get交互&#xff1f; 2.1.Query参数格式的Get请求 2.2.Path参数格式的Get请求 二.Post请求&#xff1a; 1.什么是Post请求&#xff1f; 2.前后端如何使用Post交互&#xff1f; 三.Put请求&#xf…

数据库操作与集成:使用Python与SQLite、MySQL、PostgreSQL等数据库

目录 引言 一、Python与SQLite的集成 1.1 SQLite简介 1.2 连接SQLite数据库 1.3 创建表 1.4 插入数据 1.5 查询数据 1.6 更新和删除数据 二、Python与MySQL的集成 2.1 MySQL简介 2.2 安装与配置 2.3 连接MySQL数据库 2.4 创建表与插入数据 2.5 查询、更新与删除数…

笔记:《利用Python进行数据分析》之apply的应用

这一节较难&#xff0c;十分灵活&#xff0c;可多花点时间 apply的简单应用 最通用的GroupBy方法是apply。 apply会将待处理的对象拆分成多个片段&#xff0c;然后对各片段调用传入的函数&#xff0c;最后尝试将各片段组合到一起。 回到之前那个小费数据集&#xff0c;假设你…

(四)vForm 动态表单自定义组件、属性

系列文章目录 (一)vForm 动态表单设计器之使用 (二)vForm 动态表单设计器之下拉、选择 (一)vForm 动态表单设计器之使用 文章目录 前言 一、自定义字段组件 1. 获得自定义组件json 2. 源码修改 二、自定义属性面板 1.属性面板文件 2.添加自定义属性 3.为字段组件添加属…

同事用10分钟给公司做了一套数据大屏,实力选手非他莫属!

数据可视化大屏是什么&#xff1f; 数据可视化大屏是一种将大量数据以图形、图表、地图等直观形式展示在大屏幕上。它通常被应用于企业的监控中心、会议室、展厅等场所&#xff0c;用于实时展示企业的关键业务指标、运营数据、市场趋势等信息。 今天给大家分享用JVS-智能BI如何…

DBdoctor快速纳管GBase 8a数据库

目录 如何快速纳管GBase 8a&#xff1f; 1.GBase 8a分析型数据库纳管部署架构 2.一分钟零依赖DBdoctor Server安装 3.快速纳管GBase 8a 重点说明&#xff1a; 针对GBase 8a&#xff0c;DBdoctor提供哪些功能服务&#xff1f; 1.SQL审核 2.深度巡检与报表 3.性能洞察 1&…

WebSocket通信学习笔记

1 简介 WebSocket是一种全双工通信协议&#xff0c;它允许客户端和服务器之间建立持久化的双向连接&#xff0c;从而在不频繁创建HTTP请求的情况下进行实时数据传输。与传统的HTTP协议相比&#xff0c;WebSocket更适合需要实时数据更新的应用场景&#xff0c;如聊天应用、实时…

架构师篇-23、工作坊实战应用架构

复习 ADM - 应用架构【AA】 案例实践 - 应用组件 - 核心模块 案例实践 - xx 项目应用关系 课程内应用架构

科研绘图系列:R语言PCoA图(PCoA plot)

介绍 PCoA(主坐标分析,Principal Coordinate Analysis)是一种多维数据的降维技术,它用于探索高维空间中样本之间的关系。PCoA通常用于生态学、遗传学和其他领域的数据分析,以揭示样本或个体之间的相似性或差异性。 PCoA图的作用: 数据降维:PCoA可以将高维数据(如物种…

18046 字母分类统计

### 思路 1. 读取输入的一行字符。 2. 初始化计数器&#xff1a;字母、数字、空格和其它字符的个数。 3. 遍历每个字符&#xff0c;根据其类型更新相应的计数器。 4. 输出计数结果&#xff0c;格式为&#xff1a;字母、数字、空格和其它字符的个数&#xff0c;中间以空格分隔。…

【2024-2025源码+文档+调试讲解】公开课管理系统

摘 要 随着互联网技术的迅猛发展&#xff0c;教育行业也逐渐迎来了一场全新的变革。在线教育平台的崛起为学习者提供了更加便捷灵活的学习方式&#xff0c;而公开课作为其中的一种形式&#xff0c;因其开放性和多样性而备受欢迎。然而&#xff0c;传统的公开课管理方式存在着…

【案例】如何做B端竞品分析?

竞品分析是产品经理的基本功&#xff0c;B端产品经理同样也需要经常做竞品分析。 B端产品的竞品分析难度更大&#xff0c;主要体现在如下几个方面&#xff1a; 1&#xff09;B端产品的信息获取困难 产品试用成本高&#xff0c;不像互联网产品那样可以随时下载体验。 对外公…

【数学分析笔记】第3章第1节 函数极限(1)

3. 函数极限与连续函数 3.1 函数极限 设有一半径为 r r r的圆&#xff0c;角度 x x x用弧度制表示。 红色的弧长为 2 x r 2xr 2xr&#xff0c;蓝色的弦长为 2 r sin ⁡ x 2r\sin x 2rsinx y 弦长 弧长 sin ⁡ x x y\frac{弦长}{弧长}\frac{\sin x}{x} y弧长弦长​xsinx​…

云计算密钥管理的重要性

云计算密钥管理是指对云计算环境中使用的加密密钥进行全生命周期的管理过程&#xff0c;包括密钥的生成、存储、分发、使用、更新和销毁等环节。这一过程对于保障云计算数据的安全性至关重要。以下是对云计算密钥管理的详细阐述&#xff1a; 一、云计算密钥管理的重要性 随着云…