位图与布隆过滤器 —— 海量数据处理

news2024/9/22 21:33:53

🌈 个人主页:Zfox_
🔥 系列专栏:C++从入门到精通

目录

  • 🚀 位图
    • 一: 🔥 位图概念
    • 二: 🔥 位图的实现思路及代码实现
    • 三: 🔥 位图的应用
    • 四: 🔥 STL中的 bitset
  • 🚀 布隆过滤器
    • 一: 🔥 布隆过滤器提出
    • 二: 🔥 布隆过滤器概念
    • 三: 🔥 布隆过滤器的误判率推导
    • 四: 🔥 布隆过滤器的实现
    • 五: 🔥 布隆过滤器的删除
    • 六: 🔥 布隆过滤器的应用
  • 🚀 哈希切分
    • 🔥 应用一
    • 🔥 应用二
  • 🚀 共勉

🚀 位图

一: 🔥 位图概念

🥝 所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。

💢 我们来看一道十分经典的面试题

给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。【腾讯】

  1. 遍历,时间复杂度O(N)
  2. 排序(O(NlogN)),利用二分查找: logN
  3. 位图解决
    数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。
  • 位图的解法差不多是这道题的最优解,只需要将所有数据读入后将对应位置置1,然后再查找那个数据所储的位置是否为1即可。

二: 🔥 位图的实现思路及代码实现

🥝 位图的实现思路:

🎯 为了方便实现,位图的底层可以使用一个vector。而开空间并不根据数据的个数来开,而是根据数据的范围来开(如果开的空间不够,可能有位置无法映射到)。并且一个整型具有32个字节,所以如果我们要存N个数据,就只需要开N / 32 + 1的空间即可(+1是为了防止数据小于32和向上取整)。

🎯 当要操作一个数据时,先将其除以32来判断它应该处于数组中哪一个整型中。再对其%32,来判断它位于这个整型中的哪一个位上,此时再进行对应的位运算即可。

💢 代码实现及说明如下:

template<size_t N>
class bitset
{
public:
	bitset()
	{
		_bs.resize(N / 32 + 1);
	}

	// x映射的位标记成1
	void set(size_t x)
	{
		size_t i = x / 32;
		size_t j = x % 32;

		_bs[i] |= (1 << j);
	}

	// x映射的位标记成0
	void reset(size_t x)
	{
		size_t i = x / 32;
		size_t j = x % 32;

		_bs[i] &= (~(1 << j));
	}

	// x映射的位是1返回真
	// x映射的位是0返回假
	bool test(size_t x)
	{
		size_t i = x / 32;
		size_t j = x % 32;

		return _bs[i] & (1 << j);
	}

private:
	std::vector<int> _bs;
};

三: 🔥 位图的应用

  • 💢 给定100亿个int,1G内存,设计算法找到只出现一次的整数。

首先,1G内存大约有80亿的bit位,而100亿个int,int 最多能表示大约42亿9千万个数,也就是说100亿的数据一半以上都是重复的;我们只用43亿个bit位就可以解决该问题,所以这里使用1G空间完全可以解决该问题。

这是一个KV统计搜索模型,我们可以使用两个位图来解决,用两个位图中对应位置的值来表示这个整数的出现情况:

0次 —> 00
1次 —> 01
2次及以上 —> 10

  • 🥝 我们可以复用上面我们自己实现的 bitset 去重新封装一个 twobitset

代码实现及说明如下:

template<size_t N>
class twobitset
{
public:
	void set(size_t x)
	{
		bool bit1 = _bs1.test(x);
		bool bit2 = _bs2.test(x);

		if (!bit1 && !bit2) // 00->01
		{
			_bs2.set(x);
		}
		else if (!bit1 && bit2) // 01->10
		{
			_bs1.set(x);
			_bs2.reset(x);
		}
		else if (bit1 && !bit2) // 10->11
		{
			_bs1.set(x);
			_bs2.set(x);
		}
	}

	// 返回0 出现0次数
	// 返回1 出现1次数
	// 返回2 出现2次数
	// 返回3 出现2次及以上
	int get_count(size_t x)
	{
		bool bit1 = _bs1.test(x);
		bool bit2 = _bs2.test(x);

		if (!bit1 && !bit2)
		{
			return 0;
		}
		else if (!bit1 && bit2)
		{
			return 1;
		}
		else if (bit1 && !bit2)
		{
			return 2;
		}
		else
		{
			return 3;
		}
	}

private:
	bitset<N> _bs1;
	bitset<N> _bs2;
};

🍊 这样我们就通过两个位图巧妙的解决了这个问题。

四: 🔥 STL中的 bitset

🎯 bitset官方文档

🍊 stl中的 bitset底层是一个静态数组,是在栈上开辟的空间,所以需要注意栈溢出的风险。

🍐 位图的优缺点:

优点:增删改查快、节省空间
缺点:只适用于整形

🚀 布隆过滤器

一: 🔥 布隆过滤器提出

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

1. 用哈希表存储用户记录,缺点:浪费空间。

2. 用位图存储用户记录,缺点:位图一般只能处理整形,如果内容编号是字符串,就无法处理了。
3. 将哈希与位图结合,即布隆过滤器。

二: 🔥 布隆过滤器概念

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

🍐 布隆过滤器的思路就是把key先映射转成哈希整型值,再映射一个位,如果只映射一个位的话,冲突率会比较多,所以可以通过多个哈希函数映射多个位,降低冲突率。 布隆过滤器这里跟哈希表不一样,它无法解决哈希冲突的,因为他压根就不存储这个值,只标记映射的位。它的思路是尽可能降低哈希冲突。判断一个值key在是不准确的,判断一个值key不在是准确的。
在这里插入图片描述

三: 🔥 布隆过滤器的误判率推导

如果大家还想更深了解可以参考下面这篇文章
💢 如何选择哈希函数个数和布隆过滤器长度 一文中,对这个问题做了详细的研究和论证。

四: 🔥 布隆过滤器的实现

哈希函数
🍐 首先需要写几个哈希函数来将字符串转换成整形,各种字符串Hash函数一文中,介绍了多种字符串转换成整数的哈希函数,并且根据冲突概率进行了性能比较,有兴趣的朋友可以自行研究一下。

//下面三个字符串转换成整形的仿函数
struct HashFuncBKDR
{
	// @detail 本 算法由于在Brian Kernighan与Dennis Ritchie的《The CProgramming Language》
	// 一书被展示而得 名,是一种简单快捷的hash算法,也是Java目前采用的字符串的Hash算法累乘因子为31。
	size_t operator()(const std::string& s)
	{
		size_t hash = 0;
		for (auto ch : s)
		{
			hash *= 31;
			hash += ch;
		}
		return hash;
	}
};
 
struct HashFuncAP
{
	// 由Arash Partow发明的一种hash算法。  
	size_t operator()(const std::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
{
	// 由Daniel J. Bernstein教授发明的一种hash算法。 
	size_t operator()(const std::string& s)
	{
		size_t hash = 5381;
		for (auto ch : s)
		{
			hash = hash * 33 ^ ch;
		}
 
		return hash;
	}
};

🍊 布隆过滤器框架实现

template<size_t N,  //最多存储的数据个数。
	size_t X = 5, 
	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;
 
		//cout << hash1 <<" "<< hash2 <<" "<< hash3 << endl;
 
		// 进行三次映射
		_bs.set(hash1);
		_bs.set(hash2);
		_bs.set(hash3);
	}
 
	// 判断每个比特位时,判断它不存在,注:不要判断它存在,因为不存在是准确的,存在是不准确的。
	bool Test(const K& key)
	{
		size_t hash1 = Hash1()(key) % M;
		if (!_bs.test(hash1))
		{
			return false;
		}
 
		size_t hash2 = Hash2()(key) % M;
		if (!_bs.test(hash2))
		{
			return false;
		}
 
		size_t hash3 = Hash3()(key) % M;
		if (!_bs.test(hash3))
		{
			return false;
		}
 
		return true; // 可能存在误判
	}
 
	// 获取公式计算出的误判率
	double getFalseProbability()
	{
		double p = pow((1.0 - pow(2.71, -3.0 / X)), 3.0);
 
		return p;
	}
 
private:
	static const size_t M = N * X;
	island::bitset<M> _bs;
};

五: 🔥 布隆过滤器的删除

  • 🎯 布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其他元素。
    在这里插入图片描述

“猪八戒” 和 “孙悟空” 映射的比特位都有第4个比特位。删除上图中 “猪八戒” 元素,如果直接将该元素所对应的二进制比特位置0,“孙悟空” 的元素也被删除了,因为这两个元素在多个哈希函数计算出的比特位上刚好有重叠。

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

🇺🇳 缺陷:

1. 无法确认元素是否真正在布隆过滤器中
2. 如果采用计数方式删除,存在计数回绕

六: 🔥 布隆过滤器的应用

首先我们分析⼀下布隆过滤器的优缺点:

💢 优点

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

💢 缺点

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

布隆过滤器在实际中的⼀些应用:

  • 爬虫系统URL去重

在爬虫系统中,为了避免重复爬取相同的URL,可以用布隆过滤器来进行URL去重。爬取到的URL可以通过布隆过滤器进行判断,已经存在的URL则可以直接忽略,避免重复的网络请求和数据处理。

  • 垃圾邮件过滤

在垃圾邮件过滤系统中,布隆过滤器可以用来判断邮件是否是垃圾邮件。系统可以将已知的垃圾邮件 的特征信息存储在布隆过滤器中,当新的邮件到达时,可以通过布隆过滤器快速判断是否为垃圾邮件,从而提高过滤的效率。

  • 预防缓存穿透

在分布式缓存系统中,布隆过滤器可以用来解决缓存穿透的问题。缓存穿透是指恶意用户请求⼀个不存在的数据,导致请求直接访问数据库,造成数据库压力过大。布隆过滤器可以先判断请求的数据是 否存在于布隆过滤器中,如果不存在,直接返回不存在,避免对数据库的无效查询。

  • 对数据库查询提效

在数据库中,布隆过滤器可以用来加速查询操作。例如:⼀个app要快速判断⼀个电话号码是否注册过,可以使⽤布隆过滤器来判断⼀个用户电话号码是否存在于表中,如果不存在,可以直接返回不存 在,避免对数据库进行无用的查询操作。如果在,再去数据库查询进行二次确认。

🚀 哈希切分

我们可以用哈希切分对海量数据处理问题

🔥 应用一

给两个⽂件,分别有100亿个query,我们只有1G内存,如何找到两个⽂件交集?

分析:假设平均每个query字符串50byte,100亿个query就是5000亿byte,约等于500G(1G约等于 10亿多Byte)

哈希表 / 红⿊树等数据结构肯定是⽆能为⼒的。

  • 解决方案1:

这个⾸先可以⽤布隆过滤器解决,⼀个文件中的query放进布隆过滤器,另⼀个文件依次查找,在的就是交集,问题就是到交集不够准确,因为在的值可能是误判的,但是交集⼀定被找到了。

  • 解决方案2:
  • 哈希切分首先内存的访问速度远大于硬盘,大文件放到内存搞不定,那么我们可以考虑切分为小文件,再放进内存处理。
  • 但是不要平均切分因为平均切分以后,每个小文件都需要依次暴力处理,效率还是太低了
  • 可以利⽤哈希切分依次读取文件中query,i=HashFunc(query)%N,N为准备切分多少分小文件,N取决于切成多少份,内存能放下,query放进第i号小文件,这样A和B中相同的query算出的 hash值i是⼀样的,相同的query就进⼊的编号相同的小文件就可以编号相同的文件直接找交集,不⽤交叉找,效率就提升了。
  • 本质是相同的query在哈希切分过程中,⼀定进⼊的同⼀个小文件Ai和Bi,不可能出现A中的的 query进⼊Ai,但是B中的相同query进⼊了和Bj的情况,所以对Ai和Bi进⾏求交集即可,不需要Ai 和Bj求交集。(本段表述中i和j是不同的整数)
  • 哈希切分的问题就是每个小文件不是均匀切分的,可能会导致某个小文件很⼤内存放不下。我们细细分析⼀下某个小文件很大有两种情况:
  1. 这个小文件中大部分是同⼀个query。
  2. 这个小文件是 有很多的不同query构成,本质是这些query冲突了。

针对情况1,其实放到内存的set中是可以放下的,因为set是去重的。针对情况2,需要换个哈希函数继续⼆次哈希切分。所以本体我们遇到大于1G小文件,可以继续读到set中找交集,若set insert时抛出了异常(set插⼊数据抛异常只可能是 申请内存失败了,不会有其他情况),那么就说明内存放不下是情况2,换个哈希函数进⾏二次哈希切分后再对应找交集。

在这里插入图片描述

🔥 应用二

给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址?

本题的思路跟上题完全类似,依次读取文件A中query, i = HashFunc(query) % 500,query 放进 Ai 号小文件,然后依次⽤ map 对每个A小文件统计 ip 次数,同时求出现次数最多的 ip或者topk ip。本质是相同的 ip 在哈希切分过程中,⼀定进⼊的同⼀个小文件Ai,不可能出现同⼀个ip进⼊ Ai 和 Aj 的情况,所以对Ai进行统计次数就是准确的ip次数。

🚀 共勉

以上就是我对 位图与布隆过滤器 —— 海量数据处理 的理解,觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~😉
在这里插入图片描述

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

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

相关文章

云原生系列 - Nginx(基础篇)

前言 学习视频&#xff1a;尚硅谷Nginx教程&#xff08;亿级流量nginx架构设计&#xff09;本内容仅用于个人学习笔记&#xff0c;如有侵扰&#xff0c;联系删学习文档&#xff1a; 云原生系列 - Nginx(基础篇) 1、简介 1.1、背景介绍 Nginx(enginex)是一个高性能的HTTP和…

SpringBoot教程(二十四) | SpringBoot集成日志AOP切面

SpringBoot教程&#xff08;二十四&#xff09; | SpringBoot集成日志AOP切面 &#xff08;一&#xff09;AOP 概要1. 什么是 AOP &#xff1f;2. 为什么要用 AOP&#xff1f;3. AOP一般用来干什么&#xff1f;4. AOP 的核心概念 &#xff08;二&#xff09;Spring AOP1. 简述2…

【芯智雲城】UDStore定制化存储模组和技术解决方案

一、方案详情&#xff1a; UDStore芯宇存储专注行业应用&#xff0c;根据不同应用场景&#xff0c;为客户提供包括车规级、工业级、工规宽温及高耐久型的存储模组产品和技术解决方案&#xff0c;可提供的产品和解决方案类型包括如下&#xff1a; 二、关键技术&#xff1a; 1&…

WLAN DNS proxy settings (Win 10)

WLAN DNS proxy settings (Win 10) 114.114.114.114 8.8.8.8

Ubuntu 22.04 安装 MySQL 8

Ubuntu 22.04 安装 MySQL 8 本文描述了Ubuntu安装MySQL 8的方法 CentOS7 的安装方法点击此处跳转 Windows 的安装方法点击此处跳转 Docker 的安装方法点击此处跳转 正文开始&#xff1a; 在一切开始之前&#xff0c;建议先切换到root #输入下方名&#xff0c;然后输入当…

【JavaSec】Java反射知识点补充

0x03反射-补充零散知识点 文章目录 0x03反射-补充零散知识点Runtime类setAccessible(true)三种命令执行的方法static变量赋值 前面学过 就不多说final变量赋值InDirect final间接赋值static final 向大佬致敬&#xff1a; https://drun1baby.top Runtime类 Runtime 类中有 …

JAVA itextpdf 段落自动分页指定固定行距打印

JAVA itextpdf 段落自动分页指定固定行距打印 前言&#xff1a;公司有个需求&#xff0c;打印的合同模板左上角要加上logo的图标。但是itext pdf 自动分页会按照默认的顶部高分页打印内容的&#xff0c;导致从第二页开始logo图标就会把合同的内容给覆盖掉了。然后尝试了挺多方法…

Electron31-ViteAdmin桌面端后台|vite5.x+electron31+element-plus管理系统Exe

原创自研Vue3Electron31ElementPlus桌面端轻量级后台管理Exe系统。 基于最新前端技术栈Vite5.x、Vue3、Electron31、ElementPlus、Vue-I18n、Echarts实战开发桌面端高颜值后台管理模板。内置4种布局模板&#xff0c;支持i18n国际化、动态权限路由&#xff0c;实现了表格、表单、…

基于Spring Boot的大学校园生活信息平台的设计与实现pf

TOC springboot523基于Spring Boot的大学校园生活信息平台的设计与实现pf 绪论 1.1 研究背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关…

记录阿里云服务器购买和域名绑定解析的流程

购买阿里云域名和服务器 因为App备案的原因&#xff0c;需要购买域名和服务器&#xff0c;这篇文章介绍在阿里云上购买相关服务的流程。 注册阿里云的流程比较简单这里不再赘述了。请参考我之前写的 阿里云账号注册详细教程 购买顺序&#xff1a;一般是先购买阿里云服务器&…

【乐吾乐大屏可视化组态编辑器】事件交互-场景交互

场景交互 在线使用&#xff1a;https://v.le5le.com/ 乐吾乐大屏可视化可以实现大屏页面与内嵌2d/3d场景相互通信&#xff0c;底层原理是利用了iframe通过postMessage发送消息。 下面以2d场景为例&#xff0c;实现步骤如下&#xff1a; 1. 首先配置场景2&#xff08;被嵌入…

Postman接口测试项目实战

第 1 章 什么是接口测试 1.1、为什么要进行接口测试 目前除了特别Low的公司外&#xff0c;开发都是前后端分离的&#xff0c;就是说前端有前端的工程师进行编码&#xff0c;后端有后端的工程师进行编码&#xff0c;前后端进行数据基本都是通过接口进行交互的。 1.2、接口测…

ant design 的 tree 如何作为角色中的权限选择之一

这种功能如何弄呢&#xff1f; 编辑的时候要让权限能选中哦。 <ProForm.Item name"permissions" label{intl.formatMessage({ id: permission_choose })}><Spin spinning{loading}><TreecheckableonExpand{onExpand}expandedKeys{expandedKeys}auto…

StringJoiner以及字符串小练习

概述 String Joiner跟String Builder一样&#xff0c;亦可以看成是一个容器&#xff0c;创建之后iu里面的内容是可变的 作用 提高字符串的操作效率&#xff0c;而且代码编写特别简洁&#xff0c;但是目前市场上很少有人用 JDK8 package stringdemo;import java.util.String…

iTOP-3A5000开发板流畅运行国产系统外加机箱就是一台电脑主机

性能强 采用全国产龙芯3A5000处理器&#xff0c;基于龙芯自主指令系统 (LoongArch)的LA464微结构&#xff0c;并进一步提升频率&#xff0c;降低功耗&#xff0c;优化性能。 桥片 采用龙芯 7A2000&#xff0c;支持PCIE 3.0、USB 3.0和 SATA 3.0.显示接口2 路、HDMI 和1路 VGA&a…

插入数据时,出现存在重复数据异常,捕获异常的信息

Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry xiaomi111-啊啊啊啊 for key edu_class.institution_account 执行插入操作的时候抛出这个异常 怎么捕获这个异常 可以判断 e instanceof 某个具体的异常 然后再进像下面操作&#xff0c;打印出来…

重生奇迹MU:‌重塑经典,‌再创辉煌

在浩瀚的游戏宇宙中&#xff0c;‌有一颗璀璨的星辰&#xff0c;‌它承载着无数玩家的青春回忆与梦想&#xff0c;‌那就是——重生奇迹MU‌。‌作为一款历经岁月洗礼的经典网游&#xff0c;重生奇迹MU不仅见证了游戏行业的蓬勃发展&#xff0c;‌更以其独特的魅力&#xff0c;…

倒计时:可添加可删除的倒计时函数

<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>倒计时</title> </head><body>…

关于医疗器械维修行业的一些思考

在当今医疗体系中&#xff0c;医疗器械维修行业扮演着不可或缺的角色。作为一名长期关注这一领域的人士&#xff0c;我对其有着一些个人的看法。 首先&#xff0c;医疗器械维修行业的重要性不言而喻。先进的医疗器械是现代医疗诊断和治疗的重要工具&#xff0c;而确保这些设备…

计算机组成原理(3):存储系统

1 存储器概述 主存储器其实就是内存&#xff01; 1.1 存储器的层次结构 ​ 存储器的三大评判指标&#xff1a;速度、容量、价格 ​ 使用任意一种存储器&#xff0c;都无法满足用户对存储器 高速、大容量、低价格 的需求&#xff0c;所以采用 多级结构 形成对应的 “存储体系“。…