BitMap详解及应用场景

Bitmap技术说明

Bitmap是一种通过位映射来高效存储和查询数据的技术,它在处理大规模数据集时能够有效地节省内存空间。Bitmap技术特别适用于需要对大量数据进行存在性检查的场景,比如用户签到、页面访问等,它可以显著节省内存空间。

例如,如果要记录10亿用户是否签到,如果使用传统的方法,如用一个长度为10亿的数组来存储用户的ID和签到状态,将会占用大量的内存。但如果采用Bitmap,则可以将这10亿个状态压缩存储到相对较少的内存中。假设每个用户的唯一标识符(如uid)是int64类型,那么传统方法可能需要大约8GB的内存,而Bitmap只需要约125MB的内存。

使用bitmap实现数据压缩

如果最大有2K个数据,数据是0到2047,如果使用fifo存储,每个数据需要11bit,那么fifo的深度为2K,总计需要11x2K=22Kbit。

如果使用bitmap的方式进行数据压缩,我们只需要64x32bit就能实现。Bitmap是如何实现数据压缩的呢?我们可以采用64x32bit的ram实现,ram中的每个bit表示一个数据。Bit位为1表示存在此数据,bit位为0表示不存在。

请看下图,采用4x8bit的ram表示32个数据,分别是0~31。图中行号为1,列号为1,的bit位代表数字0。图中行号为3,列号为2的bit位代表数字14。

由此可见,使用bitmap可以节约大量的存储空间,特别是在数据量较大时。

Bitmap有什么用

大量数据的快速排序、查找、去重

快速排序

假设我们要对0-7内的5个元素(4,7,2,5,3)排序(这里假设这些元素没有重复),我们就可以采用Bit-map的方法来达到排序的目的。

要表示8个数,我们就只需要8个Bit(1Bytes),首先我们开辟1Byte的空间,将这些空间的所有Bit位都置为0,然后将对应位置为1。

最后,遍历一遍Bit区域,将该位是一的位的编号输出(2,3,4,5,7),这样就达到了排序的目的,时间复杂度O(n)。

优点:

  • 运算效率高,不需要进行比较和移位;

  • 占用内存少,比如N=10000000;只需占用内存为N/8=1250000Byte=1.25M

缺点:

  • 所有的数据不能重复。即不可对重复的数据进行排序和查找。

  • 只有当数据比较密集时才有优势

快速去重

20亿个整数中找出不重复的整数的个数,内存不足以容纳这20亿个整数。

首先,根据"内存空间不足以容纳这05亿个整数"我们可以快速的联想到Bit-map。下边关键的问题就是怎么设计我们的Bit-map来表示这20亿个数字的状态了。其实这个问题很简单,一个数字的状态只有三种,分别为不存在,只有一个,有重复。因此,我们只需要2bits就可以对一个数字的状态进行存储了,假设我们设定一个数字不存在为00,存在一次01,存在两次及其以上为11。那我们大概需要存储空间2G左右。

接下来的任务就是把这20亿个数字放进去(存储),如果对应的状态位为00,则将其变为01,表示存在一次;如果对应的状态位为01,则将其变为11,表示已经有一个了,即出现多次;如果为11,则对应的状态位保持不变,仍表示出现多次。

最后,统计状态位为01的个数,就得到了不重复的数字个数,时间复杂度为O(n)。

快速查找

这就是我们前面所说的了,int数组中的一个元素是4字节占32位,那么除以32就知道元素的下标,对32求余数(%32)就知道它在哪一位,如果该位是1,则表示存在。

Bloom Filters

Bloom filter 是一个数据结构,它可以用来判断某个元素是否在集合内,具有运行快速,内存占用小的特点。

而高效插入和查询的代价就是,Bloom Filter 是一个基于概率的数据结构:它只能告诉我们一个元素绝对不在集合内或可能在集合内。

Bloom filter 的基础数据结构是一个 比特向量(可理解为数组)。

主要应用于大规模数据下不需要精确过滤的场景,如检查垃圾邮件地址,爬虫URL地址去重,解决缓存穿透问题等

如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(哈希表)等等数据结构都是这种思路,但是随着集合中元素的增加,需要的存储空间越来越大;同时检索速度也越来越慢,检索时间复杂度分别是O(n)、O(log n)、O(1)。

布隆过滤器的原理是,当一个元素被加入集合时,通过 K 个散列函数将这个元素映射成一个位数组(Bit array)中的 K 个点,把它们置为 1 。检索时,只要看看这些点是不是都是1就知道元素是否在集合中;如果这些点有任何一个 0,则被检元素一定不在;如果都是1,则被检元素很可能在(之所以说"可能"是误差的存在)。

BloomFilter 流程

1、 首先需要 k 个 hash 函数,每个函数可以把 key 散列成为 1 个整数;

2、初始化时,需要一个长度为 n 比特的数组,每个比特位初始化为 0;

3、某个 key 加入集合时,用 k 个 hash 函数计算出 k 个散列值,并把数组中对应的比特位置为 1;

4、判断某个 key 是否在集合时,用 k 个 hash 函数计算出 k 个散列值,并查询数组中对应的比特位,如果所有的比特位都是1,认为在集合中。

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>28.1-jre</version>
</dependency>
相关推荐
ChoSeitaku3 小时前
链表交集相关算法题|AB链表公共元素生成链表C|AB链表交集存放于A|连续子序列|相交链表求交点位置(C)
数据结构·考研·链表
偷心编程3 小时前
双向链表专题
数据结构
香菜大丸3 小时前
链表的归并排序
数据结构·算法·链表
jrrz08283 小时前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
@小博的博客3 小时前
C++初阶学习第十弹——深入讲解vector的迭代器失效
数据结构·c++·学习
泉崎5 小时前
11.7比赛总结
数据结构·算法
你好helloworld5 小时前
滑动窗口最大值
数据结构·算法·leetcode
JSU_曾是此间年少6 小时前
数据结构——线性表与链表
数据结构·c++·算法
sjsjs117 小时前
【数据结构-合法括号字符串】【hard】【拼多多面试题】力扣32. 最长有效括号
数据结构·leetcode
blammmp7 小时前
Java:数据结构-枚举
java·开发语言·数据结构