【C++杂货铺】海量数据处理(位图、布隆过滤器)

2024-09-01 09:36

本文主要是介绍【C++杂货铺】海量数据处理(位图、布隆过滤器),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

🌈前言🌈

📁 位图

 📂 概念

📂 模拟实现

📂 C++中位图

 📂 位图的优缺点

📁 布隆过滤器

 📂 概念

 📂 模拟实现

 📂 应用场景

📁 海量数据处理

📁 总结

🌈前言🌈

        本期【C++杂货铺】,将介绍关于哈希表的扩展内容,即位图和布隆过滤器,以及如何通过位图和布隆过滤器解决海量数据处理问题。

        哈希表作为一种数据结构,可以达到O(1)的时间复杂度内找到数据,但是C++中哈希表作为一种数据结构,是在内存中处理数据,如何解决上亿数据的查找问题呢?因此就引入了位图和布隆过滤器。

        本期内容需要你了解一定哈希表的内容,可以阅览下面这篇文章:

【C++杂货铺】unordered系列容器_unordered c++-CSDN博客

📁 位图

 📂 概念

        我们使用哈希表就是为了来查找数据,那我们转换一下思想,哈希表不在用来存储数据,而是通过一个标记位,来标识该数据是否存在。

        我们可以使用一个二进制比特位来表示是否存在数据。设计一个用位置表示数据是否存在的数据结构,这个结构就是位图。

        位图本质就是一个直接定址法的哈希表,每一个整形映射一个bit位,位图提供了bit位的操作接口。

        一个int类型4字节,32位bit,就可以表示32个数据的有无。C/C++没有提供位的类型,我们就可以通过对整形进行位运算来控制对应的位。

        例如一个数据X,通过 X / 32确定存储在哪一个整形,X % 32 确定在整型的那一个位,这样就将X值映射成vector的第i个整形数据的第j位。

        我们只需要开除2^32大小(计算机内整形的最大取值范围)的位图就可以表示出上百亿数据的有无。

📂 模拟实现

    template<size_t N = -1>class BitSet{public:BitSet(){_bitset.resize(N / 32 + 1,0);}void Set(size_t n){int i = n / 32;int j = n % 32;_bitset[i] |= (1 << j);}void Reset(size_t n){int i = n / 32;int j = n % 32;_bitset[i] &= ~(1 << j);}bool Test(size_t n){	int i = n / 32;int j = n % 32;return _bitset[i] & (1 << j);}size_t Size(){return  _bitset.size();}private:std::vector<size_t> _bitset;};

📂 C++中位图

bitset - C++ Reference

 📂 位图的优缺点

        优点:增删查改速度快,节省空间。

        缺点:只能适用于整形数据。

📁 布隆过滤器

 📂 概念

        如果不是整形数据,我们就要使用布隆过滤器了。

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

        布隆过滤器复用了位图,首先将数据通过多个哈希函数映射成不同的值,将对应的值映射到特定的位上,只要映射的位全部为1,就可以确定该数据是存在的。

        我们可以看到通过哈希函数可能产生哈希冲突,但是布隆过滤器并不用来存储数据,而是用来判断数据是否存在的,因此不需要解决冲突,而是想办法降低冲突。

        布隆过滤器降低冲突的目的就是最大程度上确定该数据是否存在,判断数据是否存在就是看是否有0的存在,如果映射值1中有一个为0,就说明该数据不存在。

        但是最坏情况下,可能存在不存在的数据,通过哈希函数,得到的映射值在整型所在位上的值为1,这就可能导致误判,即不存在的数据在映射值的位上没有找到0。

误判推导:

        这里不再过多介绍公式推导流程,我们只需要知道结论即可。即

布隆过滤器(Bloom Filter)- 原理、实现和推导_布隆过滤器原理-CSDN博客

布隆过滤器(Bloom Filter)- 原理、实现和推导_布隆过滤器原理-CSDN博客

删除问题:

        布隆过滤器默认是不支持删除的,因为比如"猪八戒“和”孙悟空“都映射在布隆过滤器中,他们映射的位有⼀个位是共同映射的(冲突的),如果我们把孙悟空删掉,那么再去查找“猪⼋戒”会查找不到,因为那么“猪八戒”间接被删掉了。

        解决方案:可以考虑计数标记的方式,⼀个位置用多个位标记,记录映射这个位的计数值,删除时,仅仅减减计数,那么就可以某种程度支持删除。但是这个方案也有缺陷,如果⼀个值不在布隆过滤器中,我们去删除,减减了映射位的计数,那么会影响已存在的值,也就是说,⼀个确定存在的值,可能会变成不存在,这里就很坑。当然也有人提出,我们可以考虑计数方式支持删除,但是定期重建⼀下布隆过滤器,这样也是⼀种思路。

 📂 模拟实现

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 Hashfunc1 = HashFuncBKDR,class Hashfunc2 = HashFuncAP,class Hashfunc3 = HashFuncDJB>class BloomFilter{public:void Set(const K& key){size_t bit1 = Hashfunc1()(key) % M;size_t bit2 = Hashfunc2()(key) % M;size_t bit3 = Hashfunc3()(key) % M;_bits.Set(bit1);_bits.Set(bit2);_bits.Set(bit3);}bool Test(const K& key){size_t bit1 = Hashfunc1()(key) % M;if (!_bits.Test(bit1))	return false;size_t bit2 = Hashfunc2()(key) % M;if (!_bits.Test(bit2))	return false;size_t bit3 = Hashfunc3()(key) % M;if (!_bits.Test(bit3))	return false;return true;}private:static const size_t M = N * X;BitSet<M> _bits;};void TestBloomFilter1(){std::string strs[] = { "百度","字节","腾讯" };BloomFilter<10> bf;for (auto& s : strs){bf.Set(s);}for (auto& s : strs){std::cout << bf.Test(s) << std::endl;}for (auto& s : strs){std::cout << bf.Test(s + 'a') << std::endl;}std::cout << bf.Test("摆渡") << std::endl;std::cout << bf.Test("百度") << std::endl;}
}

 📂 应用场景

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

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

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

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

📁 海量数据处理

1. 给40亿个不重复的无符号整数,没排过序。给⼀个无符号整数,如何快速判断⼀个数是否在这40亿个数中。

        我们可以确定的就是排除暴力查找的方案,也可以排除排序+二分查找的方案,因为大部分内存都不支持开辟这么大存储空间。

        这里我们就可以使用位图。只需要开辟2^32个位,即500M左右的空间大小就可以查找数据是否存在,并且时间复杂度为O(1)。

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

 (1)可以使用布隆过滤器来解决,一个文件的query放入布隆过滤器中,另一个文件一次查找,在的就是交集。但是问题就是可能产生误判,但一定能找到交集。 

 (2)哈希切分,因为对内存的访问肯定大于对硬盘的访问,因此我们将大文件切分成多个小文件,将小文件放入内存处理。

        但是这里不是平均切分,而是进行哈希切分,通过哈希函数,将得到映射值相同的query放入一个小文件中。这样我们只需要在内存中将Ai和Bi找交集即可。

        但是每个小文件可能不是均匀的,例如情况一出现多个相同的query或者情况二有多个映射值相同的query,导致小文件过大。针对情况一我们可以将配合数据结构set去重;针对情况二我们对小文件进行二次哈希切分。

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

📁 总结

        以上就是本期【C++杂货铺】的主要内容了,讲解了实际情况下,如何处理海量数据的问题,以及我们可以通过哈希切分的方案将大文件切分成小文件放入内存处理,提高了处理效率。

        如果感觉本期内容对你有帮助,欢迎点赞,关注,收藏Thanks♪(・ω・)ノ

这篇关于【C++杂货铺】海量数据处理(位图、布隆过滤器)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1126568

相关文章

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现

c++的初始化列表与const成员

初始化列表与const成员 const成员 使用const修饰的类、结构、联合的成员变量,在类对象创建完成前一定要初始化。 不能在构造函数中初始化const成员,因为执行构造函数时,类对象已经创建完成,只有类对象创建完成才能调用成员函数,构造函数虽然特殊但也是成员函数。 在定义const成员时进行初始化,该语法只有在C11语法标准下才支持。 初始化列表 在构造函数小括号后面,主要用于给

2024/9/8 c++ smart

1.通过自己编写的class来实现unique_ptr指针的功能 #include <iostream> using namespace std; template<class T> class unique_ptr { public:         //无参构造函数         unique_ptr();         //有参构造函数         unique_ptr(