【C++】哈希的应用——布隆过滤器

news2024/9/28 21:20:30

哈希的应用——布隆过滤器

在这里插入图片描述

文章目录

  • 哈希的应用——布隆过滤器
  • 一、布隆过滤器的概念与性质
    • 1.布隆过滤器的引出
    • 2.布隆过滤器的概念
    • 3.布隆过滤器的误判
    • 4.布隆过滤器的应用场景
    • 5.布隆过滤器优缺点
    • 6.如何选择哈希函数个数和布隆过滤器长度
  • 二、布隆过滤器的实现
    • 1.布隆过滤器基本框架
    • 2.布隆过滤器的Set插入
    • 3.布隆过滤器的Test查找
    • 4.布隆过滤器的删除

一、布隆过滤器的概念与性质

1.布隆过滤器的引出

我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉那些已经看过的内容。问题来了,新闻客户端推荐系统如何实现推送去重的? 用服务器记录了用户看过的所有历史记录,当推荐系统推荐新闻时会从每个用户的历史记录里进行筛选,过滤掉那些已经存在的记录。 如何快速查找呢?

  1. 用哈希表存储用户记录,缺点:浪费空间
  2. 用位图存储用户记录,缺点:位图一般只能处理整形,如果内容编号是字符串,就无法处理了。但我们可以使用一些哈希算法把字符串类型转换成整型,比如BKDR哈希算法,但是这里还存在一个问题。字符串的组合方式太多了,一个字符的取值有256种,一个数字只有10种,所以不可避免会出现哈希冲突

上述法二将哈希与位图结合的方法,即布隆过滤器


2.布隆过滤器的概念

布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。相比于传统的 List、Set、Map 等数据结构,此种方式不仅可以提升查询效率,也可以节省大量的内存空间。

image-20230413164108023


3.布隆过滤器的误判

布隆过滤器是**一个大型位图(bit数组或向量) + 多个无偏哈希函数**

image-20230413173915739

如果我们要映射一个值到布隆过滤器中,我们需要使用多个不同的哈希函数生成多个哈希值**,**并对每个生成的哈希值指向的 bit 位置 1,例如针对值 “baidu” 和三个不同的哈希函数分别生成了哈希值 2、4、7,则上图转变为:

image-20230413174522842

现在,如果我们要查询"baidu"这个字符串是否存在,就要判断位图中下标2,4,7对应的值是否均为1,若是,则说明此字符串“可能”存在。注意这里就可能出现误判了,至于为什么我们先再存一个字符串"tencent",假设哈希函数返回3,4,8,则对应的图如下:

image-20230413174539007

  • 值得注意的是,4 这个 bit 位由于两个值的哈希函数都返回了这个 bit 位,因此它被覆盖了。现在我们如果想查询 “alibaba” 这个值是否存在,哈希函数返回了 2、5、8三个值,结果我们发现 5 这个 bit 位上的值为 0,说明没有任何一个值映射到这个 bit 位上,因此我们可以很确定地说 “alibaba” 这个值不存在。而当我们需要查询 “baidu” 这个值是否存在的话,那么哈希函数必然会返回 2、4、7,然后我们检查发现这三个 bit 位上的值均为 1,那么我们可以说 “baidu” 存在了么?答案是不可以,只能是 “baidu” 这个值可能存在(发生了误判)。
  • 这是为什么呢?答案很简单,因为随着增加的值越来越多,被置为 1 的 bit 位也会越来越多,这样某个值 “taobao” 即使没有被存储过,但是万一哈希函数返回的三个 bit 位都被其他值置位了 1 ,那么程序还是会判断 “taobao” 这个值存在。像上面的字符串baidu,哈希函数返回的是2,4,7,可是先前的字符串baidu,哈希函数返回的是3,4,8,你怎么知道比特位4的值对应的是字符串baidu呢?我说它是字符串baidu的也没毛病吧,因此“baidu”可能存在。这就是误判出现的典型现象。

**总结:**布隆过滤器是无法解决误判的问题的,一个key通过多种哈希函数映射多个比特位只能说是降低误判的概率,但无法去除。


4.布隆过滤器的应用场景

根据布隆过滤器的概念,我们得知,只要数据允许误判,并且不会对业务造成影响,就允许使用布隆过滤器,有如下场景。

1、注册的时候,快速判断一个昵称是否使用过

  • 如果一个不在布隆过滤器里头,表示没有用过;如果在,就需要再去数据库确认查找一遍

2、黑名单

  • 如果一个人不在布隆过滤器里头,表示可同行;如果在,需要再去系统确认

3、过滤层,提高查找数据效率

  • 如果一个数据在布隆过滤器里头,接着去数据系统中查找具体的那个;如果不在,直接返回,可以不用进行后续昂贵的查询请求。

4、对爬虫网址进行过滤,爬过的不用再爬;

……


5.布隆过滤器优缺点

优点:

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

缺点:

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

6.如何选择哈希函数个数和布隆过滤器长度

  • 很显然,过小的布隆过滤器很快所有的 bit 位均为 1,那么查询任何值都会返回“可能存在”,起不到过滤的目的了。布隆过滤器的长度会直接影响误报率,布隆过滤器越长其误报率越小。
  • 另外,哈希函数的个数也需要权衡,个数越多则布隆过滤器 bit 位置位 1 的速度越快,且布隆过滤器的效率越低;但是如果太少的话,那我们的误报率会变高。

这是一位大佬绘制出来的一幅图,详细的说明了误判率和哈希函数个数及布隆过滤器长度之间的关系:

image-20230413164658533

如何选择适合业务的哈希函数的个数和布隆过滤器长度呢,一大佬给出的一个公式:

image-20230413164728151

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

我们可以大概估算一下如果使用3个哈希函数,k = 3,ln2≈0.7,k = m/n * 0.7

通过计算得知m和n的关系大概是m = 4.3n,也就是布隆过滤器的长度应该是插入元素个数的4倍。


二、布隆过滤器的实现

1.布隆过滤器基本框架

  • 这里布隆过滤器要实现成一个模板类,因为布隆过滤器插入的元素类型不固定(整型、字符串……),正因为元素类型不固定,所以要通过哈希函数把数据类型转换为整型。但一般情况下布隆过滤器都是用来处理字符串的,所以这里可以将模板参数K的缺省类型设置为string。这里我们假定传入3个哈希函数,通过上述计算,布隆过滤器的长度大概是插入元素个数的四倍。
  • 布隆过滤器的成员也是一个位图,我们可以在布隆过滤器设置一个非类型模板参数M,用于调用者指定位图的长度。
template<size_t N, 
	size_t X = 5,
	class K = string,
	class HashFunc1 = BKDRHash,
	class HashFunc2 = APHash,
	class HashFunc3 = DJBHash,
	class HashFunc4 = JSHash>

布隆过滤器的三个哈希函数的作用是把数据转换成三个不同的整型,便于后续建立映射关系,这里我们使用BKDRHash、APHash和DJBHash这三种算法:

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

struct APHash
{
	size_t operator()(const string& key)
	{
		unsigned int hash = 0;
		int i = 0;

		for (auto ch : key)
		{
			if ((i & 1) == 0)
			{
				hash ^= ((hash << 7) ^ (ch) ^ (hash >> 3));
			}
			else
			{
				hash ^= (~((hash << 11) ^ (ch) ^ (hash >> 5)));
			}
            
			++i;
		}
		return hash;
	}
};

struct DJBHash
{
	size_t operator()(const string& key)
	{
		unsigned int hash = 5381;

		for (auto ch : key)
		{
			hash += (hash << 5) + ch;
		}

		return hash;
	}
};

struct JSHash
{
	size_t operator()(const string& s)
	{
		size_t hash = 1315423911;
		for (auto ch : s)
		{
			hash ^= ((hash << 5) + ch + (hash >> 2));
		}
		return hash;
	}
};

其它哈希算法的链接:各种字符串Hash函数算法


2.布隆过滤器的Set插入

布隆过滤器的插入就是提供一个Set接口,核心思想就是把插入的元素通过三个哈希函数获取对应的整型并%比特位数从而获得对应的3个映射位置,再把这三个位置置为1即可。

image-20230413174522842

//set插入
void set(const K& key)
{
	size_t hash1 = HashFunc1()(key) % (N * X);
	size_t hash2 = HashFunc2()(key) % (N * X);
	size_t hash3 = HashFunc3()(key) % (N * X);
	size_t hash4 = HashFunc4()(key) % (N * X);
	
    _bs.set(hash1);
	_bs.set(hash2);
	_bs.set(hash3);
	_bs.set(hash4);
}

3.布隆过滤器的Test查找

布隆过滤器的查找就是提供一个Test接口,实现规则如下:

  1. 把测试数据通过三个哈希函数获取对应的整型并%比特位数从而获得对应的3个映射位置
  2. 如果三个位置中有任何一个位置不是1,直接返回false,说明查找的值不可能存在
  3. 只有三个位置全部为1,才可返回true,但是可能会存在误判(上面已经讲过)
//test查找
bool test(const K& key)
{
	size_t hash1 = HashFunc1()(key) % (N * X);
	size_t hash2 = HashFunc2()(key) % (N * X);
	size_t hash3 = HashFunc3()(key) % (N * X);
	size_t hash4 = HashFunc4()(key) % (N * X);

	if (!_bs.test(hash1))
	{
		return false;
	}
			
	if (!_bs.test(hash2))
	{
		return false;
	}

	if (!_bs.test(hash3))
	{
		return false;
	}

	if (!_bs.test(hash4))
	{
		return false;
	}
    
	// 前面判断不在都是准确,不存在误判
	return true; // 可能存在误判,映射几个位置都冲突,就会误判
}

4.布隆过滤器的删除

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

  • 比如:删除上图中"create"元素,如果直接将该元素所对应的二进制比特位置0,“source”元素也被删除了,因为这两个元素在多个哈希函数计算出的比特位上刚好有重叠。

一种支持删除的方法(计数法删除):

  • 将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计器减一,通过多占用几倍存储空间的代价来增加删除操作。

image-20230413164803445

缺陷:

  1. 无法确认元素是否真正在布隆过滤器中
  2. 存在计数回绕

总结:

  • 布隆过滤器不支持直接删除归根结底在于其主要就是用来节省空间和提高效率的,在计数法删除时需要遍历文件或磁盘中确认待删除元素确实存在,而文件IO和磁盘IO的速度相对内存来说是很慢的,并且为位图中的每个比特位额外设置一个计数器,就需要多用原位图几倍的存储空间,这个代价也是不小的。若支持删除就不那么节省空间了,也就违背了布隆过滤器的本质需求。

相关参考文献链接:布隆过滤器的原理,使用场景和注意事项

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

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

相关文章

机器学习——SVM原理

问&#xff1a;支持向量机是基于经验风险最小化(ERM) 原则构建的&#xff0c;因此有更好的泛化性能。 答&#xff1a;错误。支持向量机是一种基于结构风险最小化原则构建的机器学习算法&#xff0c;它可以通过寻找合适的分割超平面来实现分类任务&#xff0c;并且具有较好的泛…

设计模式:软件设计原则

文章目录 1.开闭原则2.里氏代换原则3.依赖倒转原则4.接口隔离原则5.迪米特法则6.合成复用原则 在软件开发中&#xff0c;为了提高软件系统的可维护性和可复用性&#xff0c;增加软件的可扩展性和灵活性&#xff0c;程序员要尽量根据6条原则来开发程序&#xff0c;从而提高软件开…

QGIS绘制一张地图学习笔记01——配准栅格数据并解决配准导出的栅格文件不能显示的问题

1、进入配准工具主页面 首先&#xff0c;打开我们的绘制底图或者叫配准目标底图&#xff0c;我这里用的是高德的在线地图&#xff0c;具体qgis加载在线地图的方法见我前面的章节。加载的在线地图如下所示。 接着我们点击上方菜单栏的 栅格 菜单&#xff0c;点击菜单中的 配准工…

软件测试-测试用例案例及思维导图展示

目录 自动售货机的测试用例 一个杯子的测试用例 一支笔的测试用例 朋友圈点赞的测试用例 登录模块 购物车的测试用例 Windows对文件的复制粘帖功能的测试用例 自动售货机的测试用例 一个杯子的测试用例 一支笔的测试用例 朋友圈点赞的测试用例 功能测试 1点赞后是否显示…

docker安装rabbitMq集群

一 机器准备 准备三台虚拟机&#xff1a; 192.168.56.102 102.168.56.103 102.168.56.104 二 拉取镜像 在三台虚拟机上分别执行&#xff1a; docker pull rabbitmq:3.9.5-management 三 启动容器 103: docker run -d --hostname node2 --ad…

React--》React组件变化每次都会导致重新渲染,如何解决?

目录 React.memo useCallback useMemo React.memo React组件会在两种情况下下发生渲染 第一种&#xff1a;当组件自身的state发生变化时 第二种&#xff1a;当组件的父组件重新渲染时 第一种情况下重新渲染无可厚非&#xff0c;state都变化了组件自然应该重新进行渲染&…

7.1 基本运放电路(1)

集成运放的应用首先表现在它能构成各种运算电路上&#xff0c;并因此而得名。在运算电路中&#xff0c;以输入电压作为自变量&#xff0c;以输出电压作为函数&#xff1b;当输入电压变化时&#xff0c;输出电压将按一定的数学规律变化&#xff0c;即输出电压反映输入电压某种运…

【Web】WebHook详解

文章目录 webhook简介什么是 webhook?webhook 有什么用?webhook请求过程使用 webhookWebhook POST 或 GETWebhook 与轮询何时使用 webhookAsp .Net接受与处理接口处理 发送 WebHook和消息队列区别与联系来源 webhook简介 在当今高度连接的网络世界中,没有什么可以孤立地发挥…

Vue3二维码(QRCode)

可自定义设置以下属性&#xff1a; 扫描后的文本或地址&#xff08;value&#xff09;&#xff0c;类型&#xff1a;string&#xff0c;默认 二维码大小&#xff08;size&#xff09;&#xff0c;类型&#xff1a;number&#xff0c;单位px&#xff0c;默认 160 二维码颜色&…

nginx 部署vue项目,路由模式为history时,页面刷新404问题

目录 情况说明本案例解决方法配置解释为什么会出现404的情况root 和 alias 的区别try_files 配置的作用 友情提示 情况说明 nginx部署vue项目&#xff0c;文件放在html下的dist文件夹中 nginx.conf 文件中&#xff0c;server 里配置文件的位置、请求跨域等信息 本案例解决方…

Ubuntu Linux操作

引言 晚上上课发现桌子上遗留了这本书&#xff0c;水课就看了看学习下&#xff0c;以下内容直接总结知识点 磁盘内存解析 (1)硬盘有数个盘片,每个盘片两个面,每个面一个磁头。 (2)盘片被划分为多个扇形区域即扇区。 (3)同一盘片不同半径的同心圆为磁道。 (4)不同盘片相同半径…

制作真人手办有哪些不便?怎么解决?

相信很多朋友都喜欢拍摄写真&#xff0c;比如孩子生日的时候&#xff0c;结婚纪念的时候&#xff0c;写真照片能留存住很多美好的记忆。 不过随着科技的发展&#xff0c;大家已经不能满足只靠照片来记录生活了&#xff0c;越来越多的人开始盯上了手办这件物品。将真人的照片和…

4.5 创建透视表与交叉表

4.5 创建透视表与交叉表 4.5.1利用pivot_table函数可以实现透视表pivot_table函数的常用参数及其说明 4.5.2 使用crosstab函数创建交叉表crosstab函数的常用参数及其说明 4.5.3 任务实现数据完整代码 数据透视表&#xff08;Pivot Table&#xff09;是数据分析中一种常用的工具…

018 - C++ 类和结构体中的静态(static)

上一期我们讨论了 C 中的 static 关键字以及它在类或结构体之外的意义。本期我们讨论 static 在一个类或一个结构体中的具体情况。 先了解这些 在几乎所有面向对象的语言中&#xff0c;静态在一个类中意味着特定的东西。这意味着在类的所有实例中&#xff0c;这个变量只有一个…

【c#串口通信从小白到大神(5)】如何打开串口

1、新建一个winform程序 这里以visual studio 2019 社区版为例,关于visual studio 2019 社区版的下载链接请点击这里:如何下载安装visual studio 2019 社区版 第1步、打开visual studio 2019 ,出现下图: 第2步、点击“创建新项目”,如下图: 第3步、选择 “Windows 窗…

深眸科技围绕机器视觉技术,加速实现制造行业生产线智能化升级

工业4.0时代&#xff0c;是以智能制造为代表的第四次工业革命时代。随着人工智能的高速发展&#xff0c;机器视觉作为当前制造业质量控制领域的重要技术之一&#xff0c;在各行各业的应用逐渐广泛&#xff0c;其行业市场需求进一步飙升。据GGII预测&#xff0c;预计2025年&…

openEuler实验-使用Shell脚本实现局域网MAC地址收集和FTP服务监控

linux的简单运用 目录 前言 1. 实验目的 2. 实验内容 3. 实验知识点 4. 实验时长 5. 实验环境 实验分析 部署FTP服务器 1&#xff09;安装FTP服务 2&#xff09;查看FTP服务 3&#xff09;启动FTP服务 4&#xff09;关闭FTP服务 收集MAC地址 1&#xff09;修改主…

C++17字符流以及C++11文件流以及IO流

getline() 有时候我们希望在最终的字符串中保留输入时的空白符&#xff0c;这时候应该用getline函数来替代原来的>>运算符。&#xff08; cin 不能输入包含嵌入空格的字符串&#xff09;。 getline()函数的参数是一个输入流和一个string对象&#xff0c;原型是&#xf…

threejs的使用

threejs介绍&#xff1a; Three.js是一款基于WebGL的JavaScript 3D库&#xff0c;用于创建和渲染3D图形场景。它提供了一个简单易用的接口&#xff0c;让开发者可以通过JavaScript代码创建出高度交互性和可视化的3D场景。Three.js提供了很多可用的3D对象和材质&#xff0c;例如…

C++ -2- 类和对象(上)| 什么是类

​ 文章目录 1.面向过程与面向对象2.类的引入3.类的定义两种定义方式 4.类的访问限定符5.类的作用域6.类的示例化7.类的对象大小计算8.类成员函数的this指针C语言和C的对比(this指针)空指针的问题 C语言和C实现Stack对比 1.面向过程与面向对象 C&#xff1a;面向过程&#xf…