海量数据处理(一) :位图与布隆过滤器的概念以及实现
目录
- 位图
- 位图概念
- 位图的应用
- 位图的实现思路
- set
- reset
- test
- 完整代码
- 布隆过滤器
- 布隆过滤器概念
- 布隆过滤器的优缺点
- 优点
- 缺点
- 布隆过滤器的实现思路
- 哈希冲突的问题
- 如何选择哈希函数个数和布隆过滤器长度
- 插入
- 查找
- 删除
- 完整代码
位图
位图概念
位图其实就是哈希的变形,他同样通过映射来处理数据,只不过位图本身并不存储数据,而是存储标记。通过一个比特位来标记这个数据是否存在,1代表存在,0代表不存在。
位图通常情况下用在数据量庞大,且数据不重复的情景下判断某个数据是否存在。
例如下面这道十分经典的题目
给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数
中。
关于这道题目,解法其实有很多。
1.快速排序后二分搜索。(内存可能不够,要16G内存)
2.位图处理,(40亿无符号整数用位图标记只需要512M的内存)
位图的解法差不多是这道题的最优解,只需要将所有数据读入后将对应位置置1,然后再查找那个数据所储的位置是否为1即可。
位图的应用
- 快速查找某个数据是否在一个集合中
- 排序
- 求两个集合的交集、并集等
- 操作系统中磁盘块标记
位图的实现思路
为了方便实现,位图的底层可以使用一个vector。而开空间并不根据数据的个数来开,而是根据数据的范围来开(如果开的空间不够,可能有位置无法映射到)。并且一个整型具有32个字节,所以如果我们要存N个数据,就只需要开N / 32 + 1的空间即可(+1是为了防止数据小于32和向上取整)。
当要操作一个数据时,先将其除以32来判断它应该处于数组中哪一个整型中。再对其%32,来判断它位于这个整型中的哪一个位上,此时再进行对应的位运算即可。
set
set即将对应标识位置1
可以通过将1左移pos个位置,再让对应位置与这个数据相或即可实现。
//数据的对应标识位置1
void set(size_t x)
{//计算出在数组中哪一个整型中size_t index = x >> 5;//计算出在该整型的哪一个位上size_t pos = x % 32;//对应位置 置1_bits[index] |= (1 << pos);++_size;
}
reset
reset即将对应标识位置0
首先让1左移pos个位置,再对这个数据进行取反。然后让对应位置数据与这个数据相与即可。
//数据的对应标识位置0
void reset(size_t x)
{size_t index = x >> 5;size_t pos = x % 32;//对应位置数据置零_bits[index] &= ~(1 << pos);++_size;
}
test即判断这个数据在不在,只需要让1左移pos个位置,再用对应位置进行与运算,如果为1则说明存在,0则说明不存在
test
bool test(size_t x) const
{size_t index = x >> 5;size_t pos = x % 32;return _bits[index] & (1 << pos);
}
完整代码
#pragma once
#include<vector>namespace lee
{class bitset{public://每一个位标识一个数据,一个整型4个字节,可存储32个位, 所以需要/32(或者右移五位)。+1是为了取整bitset(size_t size = 32) : _bits((size >> 5) + 1, 0), _size(0){}//数据的对应标识位置1void set(size_t x){//计算出在数组中哪一个整型中size_t index = x >> 5;//计算出在该整型的哪一个位上size_t pos = x % 32;//对应位置 置1_bits[index] |= (1 << pos);++_size;}//数据的对应标识位置0void reset(size_t x){size_t index = x >> 5;size_t pos = x % 32;//对应位置数据置零_bits[index] &= ~(1 << pos);++_size;}//判断数据是否存在bool test(size_t x) const{size_t index = x >> 5;size_t pos = x % 32;return _bits[index] & (1 << pos);}size_t size() const{return _size;}private:std::vector<int> _bits;size_t _size;};
};
布隆过滤器
我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉那些已经看
过的内容。问题来了,新闻客户端推荐系统如何实现推送去重的? 用服务器记录了用户看过的所有历史记
录,当推荐系统推荐新闻时会从每个用户的历史记录里进行筛选,过滤掉那些已经存在的记录。 如何快速查
找呢?
- 用哈希表存储用户记录,缺点:浪费空间
- 用位图存储用户记录,缺点:不能处理哈希冲突
- 将哈希与位图结合,即布隆过滤器
布隆过滤器概念
布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结 构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函
数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。
布隆过滤器的优缺点
优点
- 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关
- 哈希函数相互之间没有关系,方便硬件并行运算
- 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
- 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势
- 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
- 使用同一组散列函数的布隆过滤器可以进行交、并、差运算
缺点
- 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白 名单,存储可能会误判的数据)
- 不能获取元素本身
- 一般情况下不能从布隆过滤器中删除元素
- 如果采用计数方式删除,可能会存在计数回绕问题
布隆过滤器的实现思路
这里底层使用的数据结构是前面实现的位图,所以对应操作可以直接到上面看。
哈希冲突的问题
之前在哈希那一章说过,当字符串使用哈希时,无可避免的会出现哈希冲突的问题,而位图又是一个不能解决哈希冲突的数据结构,所以这就导致了一个问题,对于一个数据不能只有一个位置来标记,需要用到多个位置。于是我们需要用到多个哈希函数,来将数据映射到多个位置上面,才能确保数据的准确性。
例如下面的baidu,分别通过三种哈希函数映射到了1,4,7。将这三个位置全部置1
这里我使用了三个字符串哈希函数,分别是BKDR,SDBM,RS。
struct _BKDRHash
{//BKDRHashsize_t operator()(const std::string& key){size_t hash = 0;for (size_t i = 0; i < key.size(); i++){hash *= 131;hash += key[i];}return hash;}
};struct _SDBMHash
{//SDBMHashsize_t operator()(const std::string& key){size_t hash = 0;for (size_t i = 0; i < key.size(); i++){hash *= 65599;hash += key[i];}return hash;}
};struct _RSHash
{//RSHashsize_t operator()(const std::string& key){size_t hash = 0;size_t magic = 63689;for (size_t i = 0; i < key.size(); i++){hash *= magic;hash += key[i];magic *= 378551;}return hash;}
};
如何选择哈希函数个数和布隆过滤器长度
而如果一个数据要映射多个位置,如果布隆过滤器较小,则会导致数据马上全部映射满,此时无论进行什么操作,都会存在大量的误报率。也就是说,布隆过滤器的长度与误报率成反比,与空间利用率成反比。
并且哈希函数的个数也值得思考,哈希函数越多,映射的位置也就越多,此时准确性也就越高,但随之带来的问题就是效率的降低。也就是说,哈希函数的个数与效率成反比,准确率成正比
这张图则是各种长度以及哈希函数的效率对比图。
那么该如何选择哈希函数的个数以及布隆过滤器的长度呢?
这里引用一位大佬计算出的公式,具体求解链接我放在了文章末尾
k 为哈希函数个数,m 为布隆过滤器长度,n 为插入的元素个数,p 为误报率。
所以根据公式,我这里使用的哈希函数为3个,空间就应该开插入元素个数的五倍。
插入
数据分别映射到三个位置上,将三个位置全部置1
void set(const K& key)
{//为了减少错误率,用多个哈希函数将同一个数据映射到多个位置size_t pos1 = Hash1()(key) % _capacity;size_t pos2 = Hash2()(key) % _capacity;size_t pos3 = Hash3()(key) % _capacity;_bs.set(pos1);_bs.set(pos2);_bs.set(pos3);++_size;
}
查找
布隆过滤器的查找即分别查找映射位,一旦有任何一个为0,则说明数据不存在。如果全部为1,此时说明数据可能存在,因为可能存在将别人映射的位置误判进来,所以布隆过滤器的查找是不够准确的。所以可以这么说,布隆过滤器只提供模糊查询,如果需要精确查询,只能使用别的方法。
布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,因
为有些哈希函数存在一定的误判。
bool test(const K& key)
{size_t pos1 = Hash1()(key) % _capacity;size_t pos2 = Hash2()(key) % _capacity;size_t pos3 = Hash3()(key) % _capacity;if (!_bs.test(pos1) || !_bs.test(pos2) || !_bs.test(pos3)){return false;}return true;
}
删除
布隆过滤器是不支持删除操作的,因为一旦进行删除,很可能就会将别人映射的位置也置为0,导致出现错误。
但是如果非要删除的话,也不是不行。
可以将每一个比特位拓展为一个计数器,每当有数据插入时对应位置的计数器+1,数据删除是对应位的计数器-1。一个位肯定无法完成计数,需要用到多个位,此时就会导致存储空间的大量增加,使得效率下降,而本身选择布隆过滤器也是为了节省空间,这样就本末倒置了。
完整代码
#pragma once
#include"bitset.hpp"
#include<string>
namespace lee
{struct _BKDRHash{//BKDRHashsize_t operator()(const std::string& key){ size_t hash = 0;for (size_t i = 0; i < key.size(); i++){hash *= 131;hash += key[i];}return hash;}};struct _SDBMHash{//SDBMHashsize_t operator()(const std::string& key){size_t hash = 0;for (size_t i = 0; i < key.size(); i++){hash *= 65599;hash += key[i];}return hash;}};struct _RSHash{//RSHashsize_t operator()(const std::string& key){size_t hash = 0;size_t magic = 63689;for (size_t i = 0; i < key.size(); i++){hash *= magic;hash += key[i];magic *= 378551;}return hash;}};template<class K = std::string, class Hash1 = _BKDRHash, class Hash2 = _SDBMHash, class Hash3 = _RSHash>class BloomFilter{public:BloomFilter(size_t num): _bs(num), _capacity(num), _size(0){}void set(const K& key){//为了减少错误率,用多个哈希函数将同一个数据映射到多个位置size_t pos1 = Hash1()(key) % _capacity;size_t pos2 = Hash2()(key) % _capacity;size_t pos3 = Hash3()(key) % _capacity;_bs.set(pos1);_bs.set(pos2);_bs.set(pos3);++_size;}bool test(const K& key){size_t pos1 = Hash1()(key) % _capacity;size_t pos2 = Hash2()(key) % _capacity;size_t pos3 = Hash3()(key) % _capacity;if (!_bs.test(pos1) || !_bs.test(pos2) || !_bs.test(pos3)){return false;}return true;}size_t size() const{return _size;}private:lee::bitset _bs;size_t _size;size_t _capacity;};
};
参考文章:
- 详解布隆过滤器的原理、使用场景和注意事项
海量数据处理(一) :位图与布隆过滤器的概念以及实现相关推荐
- 常见数据结构和算法实现(排序/查找/数组/链表/栈/队列/树/递归/海量数据处理/图/位图/Java版数据结构)
常见数据结构和算法实现(排序/查找/数组/链表/栈/队列/树/递归/海量数据处理/图/位图/Java版数据结构) 数据结构和算法作为程序员的基本功,一定得稳扎稳打的学习,我们常见的框架底层就是各类数据 ...
- 哈希思想的应用 - 位图,布隆过滤器
目录 位图 海量数据处理问题: 哈希思想的应用:位图 位图实现: 位图处理海量数据 布隆过滤器 引: 布隆过滤器的概念: 布隆过滤器简单实现: 布隆过滤器的查找 布隆过滤器的删除 布隆过滤器优点 布隆 ...
- 哈希表、位图、布隆过滤器
目录 哈希的概念 哈希函数 哈希冲突和解决方法 闭散列 插入 查找 删除 开散列 插入 查找 删除 哈希表(开散列)整体代码 位图 位图模拟实现思路分析: 位图应用 布隆过滤器 本文介绍unorder ...
- 冰冰学习笔记:位图与布隆过滤器
欢迎各位大佬光临本文章!!! 还请各位大佬提出宝贵的意见,如发现文章错误请联系冰冰,冰冰一定会虚心接受,及时改正. 本系列文章为冰冰学习编程的学习笔记,如果对您也有帮助,还请各位大佬.帅哥.美女点点支 ...
- 数据结构-哈希-位图与布隆过滤器
位图与布隆过滤器 一,位图 题目分析 位图设计 位图代码 经典题目 二,布隆过滤器 布隆过滤器概念 布隆过滤器的插入 布隆过滤器的结构 布隆过滤器总结 经典题目 三,哈希切割 一,位图 题目分析
- 海量数据处理(位图和布隆过滤器)
哈希切割 给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址? 与上题条件相同,如何找到top K的IP?如何直接用Linux系统命令实现 解决思路 ...
- Hash,位图,布隆过滤器
文章目录 哈希概念 哈希冲突 哈希函数 除留余数法--(非常常用) 直接定址法--(常用) 平方取中法--(了解) 折叠法--(了解) 随机数法--(了解) 数学分析法--(了解 ) 哈希冲突解决 闭 ...
- 为什么亿级数据量时要使用位图?位图和布隆过滤器有什么关系?
文章目录 一.为什么要使用位图 二.什么是位图 三.布隆过滤器 四.位图的常见使用场景 4.1 生成看似无规律却呈趋势递增的号段式ID 4.2 搜索引擎爬虫网页去重 4.3 大型网站每天UV数量统计 ...
- 哈希结构(图文详解)【哈希表,哈希桶,位图,布隆过滤器】
哈希结构 哈希概念 常见的K-V结构,实现了元素关键码与元素值的映射关系,但没有实现元素关键值与元素存储位置的映射关系,在遍历过程中,一般的顺序表或搜索二叉树要进行关键值的多次比较,其中顺序表的时间复 ...
最新文章
- java语言的主要的知识点
- Dos批处理常用命令大全入门
- 商汤组了「最强大脑」局,正儿八经解释为啥搞起电竞AI
- 数据绑定控件之Repeater
- 生产服务器环境最小化安装后Centos 6.5优化配置备忘
- 阿里云移动测试平台MQC移动测试沙龙第3期【北京站】
- 框架学习之Hibernate 第十节 事务原理与分析
- Python中字符串操作函数string.split('str1')和string.join(ls)
- 使用 Spring Boot Security 进行安全控制
- 2017年网络犯罪现状分析报告
- SpringBoot配置参数绑定@ConfigurationProperties@Value
- Linux系统调用之open(), close() (转载)
- 【基础版】整数加减乘除计算器
- 玩转飞思卡尔在线调试工具FreeMaster
- android 开机动画制作
- 如何做接口测试呢?接口测试有哪些工具【小白都会系列】
- 虚拟主机需要备案吗?
- 当你一个人走过你们曾经走过的大街小巷,你会有何感受?
- 文学类的小说,言情类的,没有排行榜上的
- WMS系统--移库逻辑