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

🌈 个人主页: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次数。

🚀 共勉

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

文书智能助手

背景 司法、医疗等行业存在着大量的文书&#xff0c;一份文书或者卷宗少则几十页&#xff0c;多则几万页。在查看和检查这些文书时&#xff0c;会遇到大量的信息。当需要查询进一步的详细内容时&#xff0c;往往需要选择一下文字&#xff0c;然后再在各种系统中 查询详细的信息…

IDEA安装和使用(配图)

功能强大&#xff1a; 1、强大的整合能力&#xff0c;比如Git,Maven,Spring等 2、开箱即用&#xff08;集成版本控制系统&#xff0c;多语言支持的框架随时可用&#xff09; 3、符合人体工程学 1、高度智能 2、提示功能的快速&#xff0c;便捷&#xff0c;范围广 3、好用…

Nginx平滑升级与回滚示例

Nginx 的平滑升级和平滑回滚是确保 Web 服务高可用性的重要组成部分。这两种操作允许你在不中断服务的情况下更新或回滚 Nginx 的版本。 Nginx 平滑升级与回滚 Nginx 的平滑升级和平滑回滚是确保 Web 服务高可用性的重要组成部分。这两种操作允许你在不中断服务的情况下更新或…

Vue 生命周期详解含demo、面试常问问题案例

Vue 生命周期详解、面试常问问题案例 含 demo 文章目录 Vue 生命周期详解、面试常问问题案例 含 demo一、Vue 生命周期是什么二、Vue 中如何使用生命周期钩子1. **beforeCreate**2. **created**3. **beforeMount**4. **mounted**5. **beforeUpdate**6. **updated**7. **beforeD…

AIoTedge边缘计算平台V1.0版本发布

AIoTedge边缘计算平台V1.0&#xff0c;一款创新的AIoT解决方案&#xff0c;现已正式发布。该产品集成了NodeRED软网关、边缘物联网平台和边缘AI能力&#xff0c;为企业提供强大的边云协同能力。它支持设备管理和泛协议接入&#xff0c;确保不同设备间的无缝连接。AIoTedgeV1.0还…

Hexo通过GitHub设置自定义域名

本身GitHub也是支持自定义域名的&#xff0c;本次教程将讲解如何使用GitHub自带的自定义域名解析。 1. GitHub设置 1.1 登录GitHub账号 登录GitHub账号&#xff0c;找到名称为 用户名.github.io的仓库&#xff0c;并点击进入。 1.2 进入Settings页面 点击如图的Settings按…

https://developer.nvidia.com/cuda-toolkit-archive

CUDA Toolkit Archive | NVIDIA Developerhttps://developer.nvidia.com/cuda-toolkit-archive

一款免费的文件锁定占用解除工具,绿色免安装版

IObit Unlocker是一款由IObit公司开发的免费文件解锁工具&#xff0c;旨在解决用户在删除、重命名、移动或复制文件和文件夹时遇到的“无法删除”或“访问被拒绝”的问题。该软件体积小巧&#xff0c;不到3MB&#xff0c;非常易于使用&#xff0c;并且不需要安装&#xff0c;可…

代码随想录 day 49 单调栈

第十章 单调栈part02 42. 接雨水 接雨水这道题目是 面试中特别高频的一道题&#xff0c;也是单调栈 应用的题目&#xff0c;大家好好做做。 建议是掌握 双指针 和单调栈&#xff0c;因为在面试中 写出单调栈可能 有点难度&#xff0c;但双指针思路更直接一些。 在时间紧张的情…

volta引发的血案

什么是volta volta用于做项目级别的node版本控制&#xff0c;当手头上的项目有多个时&#xff0c;且node版本可能还不一样&#xff0c;我们需要不断切换node版本。使用volta可以很好的解决这个问题。只需要安装volta&#xff0c;然后在下面的package.json中配置好node版本即可…

初始redis:List

列表 List 相当于数组或者顺序表。 对于List来说&#xff0c;两侧都可以插入和删除&#xff0c;时间复杂度是O(1)。 有很多的操作&#xff0c;比如 llen 可以获取List的长度&#xff0c;lrem 可以删除元素 &#xff0c;lrange可以去一个字符串 &#xff0c; lindex可以根据下标…

P38-数据存储1

百度2015年系统工程师笔试题 编程题 编程题 编程题 编程题

JUC- Synchronized原理

对象头概念 以 32 位虚拟机为例 Klass Word&#xff1a;指向类对象的指针&#xff0c;标明这个对象的类型 普通对象 |--------------------------------------------------------------| | Object Header (64 bits) | |---------------…

BI分析实操案例分享:零售企业如何利用BI工具对销售数据进行分析?

在当下这个竞争激烈的零售市场&#xff0c;企业如何在波诡云谲的商场中站稳脚跟&#xff0c;实现销售目标的翻倍增长&#xff1f; 答案可能就藏在那些看似杂乱无章的数字里。 是的&#xff0c;你没有看错&#xff0c;答案正是那些我们日常接触的销售数据。它们就像是宝藏&…

设计模式(单例模式、工厂模式、建造者模式、代理模式)

设计模式是前辈们对代码开发经验的总结&#xff0c;是解决特定问题的一系列套路。它不是语法规定&#xff0c;而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案&#xff08;设计思想、设计经验&#xff09;。 一、六大原则 1、单一职责原则&#…

C语言--01基础数据类型

1.整型 概念&#xff1a;表达整数类型的数据语法&#xff1a; int a 123; // 定义了一个专门用来存储整数的变量a a 456 ; 需要注意的地方&#xff1a; int 的本意是 integer&#xff0c;即整数的意思int a 代表在内存中开辟一块小区域&#xff0c;称为 a&#xff0c;用来…

ado.net 操作sqlite

新建控制台项目 安装nuget包Microsoft.Data.Sqlite 数据库名字和链接 string dbName "test.db"; SqliteConnection? connection null; try {//创建链接connection new SqliteConnection($"Data Source{dbName}");//打开链接connection.Open(); } ca…

【Hot100】LeetCode—160. 相交链表

目录 1- 思路思路 2- 实现⭐160. 相交链表——题解思路 3- ACM 实现 原题连接&#xff1a;160. 相交链表 1- 思路 思路 首先想要找到相交点&#xff0c;需要定义连个指针。两个指针一定得是同步的&#xff0c;例如 A 链表 [1,2,3,4,5] &#xff0c;链表 B 是 [4,5] 1- 指针对…

大公报发表欧科云链署名文章:发行港元稳定币,建Web3.0新生态

欧科云链研究院资深研究员蒋照生近日与香港科技大学副校长兼香港Web3.0协会首席科学顾问汪扬、零壹智库创始人兼CEO柏亮&#xff0c;在大公报发布联合署名文章 ——《Web3.0洞察 / 发行港元稳定币&#xff0c;建Web3.0新生态》&#xff0c;引发市场广泛讨论。 文章就香港稳定币…

鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射

关于中断部分系列篇将用三篇详细说明整个过程. 中断概念篇 中断概念很多&#xff0c;比如中断控制器&#xff0c;中断源&#xff0c;中断向量&#xff0c;中断共享&#xff0c;中断处理程序等等.本篇做一次整理.先了解透概念才好理解中断过程.用海公公打比方说明白中断各个概念…