java bitset bitmap,BitMap 详解

问题引入

我有40亿个无序int类型的整数,再给一个新的整数,我需要判断新的整数是否在40亿个整数中,你会怎么做?

问题分析

最常规直观的解法,就是使用一个HashSet,将所有数都add进去,然后对要判断的数,执行一下contains函数判断下就ok了。

但是思考一下,这个解法有可行性吗?考虑一下这么做的话,这个HashSet会占用多少内存?

正常一个int整数,占用4个字节。

40亿个整数的话,就是 4,000,000,000 * 4字节 = 16,000,000,000字节 ≈ 14.9G。

我们是不是得申请一个特别大内存的机器,专门做这个运算才行?

假如我们只有一台2G的机器的话,如何实现呢?

BitMap登场

我们申请一个Bit数组,数组的每个元素,都能表示0或者1,数组的长度为2^32。

初始数组:

e49da445b47fdb63beb008363e1c08a0.png

假设40亿的整数分别为 4,7,1,5,9 …

然后,对于40亿中的每个整数,我们都将这个整数作为下标,把数组中对应的位置为1。

如图所示:

aded18cc64a39d5b8e0b7aa2e5b7c25b.png

由于一个整数占4字节,所以一个无符号整数,取值范围是0 ~ (2^32 - 1)。

因此,对于40亿整数中的任何一个数,都可以对应放进这个数组里面。

另外,可以简单的计算出来,这个数组,占用的空间大小为:

2^32 Bit = 2^32 (Bit) / 8 (Byte) / 1024 (KB) / 1024 (M) = 512M

这时,我们想要判断一个整数是否在这40亿整数中,我们只需要直接在数组中,取该数字下标的值。

如果值是1则存在,如果值是0则不存在。

是不是很简单?

对于该问题,利用BitMap,我们可以将计算时,近15G的空间占用,变成512M。

而时间复杂度不变,依然是O(N)。

总结

通过以上部分,我们知道了,BitMap可以实现海量数字中的快速定位查找,并且极其节约空间。

扩展延伸

思考以下问题:

根据微信最近公布的数据,微信App的月活跃已经破11亿了。

假设微信做了一个每日签到的功能。

签到数据按天分表存储,也就是说每一天的签到数据,都独立存在于一个表里。

这时,如果我们接到了2个查询数据的需求,分别要给出五一3天假里面:

A、3天连续打卡了的用户id集合

B、3天内任一天打过卡的用户id集合

要求用户id不重复,你会如何实现呢?

每天的打卡数据,可能都是十亿级别的。

如果把三天的用户id数据,都load到内存里的集合对象里面,再挨个对比判断,那依然会出现内存溢出的情况。

细心的同学可能会想到,是不是也可以用BitMap实现呢?答案是可以的。

我们将三天的数据,都维护在一个BitMap里,每个BitMap占用的空间差不多也是百M级别,完全能接受。

然后对于三个Bit数组,我们可以高效的执行 按位与 和 按位或 操作。

按位与出来的结果集,即为A查询的结果,3天都打卡的用户id集合。

按位或出来的结果集,即为B查询的结果,3天内任一天打过卡的用户id集合。

总结

所以,BitMap也可以利用位运算,高效的实现海量数据集合的相融和相交等操作。

Java实现和源码解析

JDK中,是直接提供的BitMap集合的实现类的:java.util.BitSet.class

基础用法如下:

BitSet bitSet = new BitSet();

bitSet.set(0, Boolean.TRUE);

bitSet.set(3, Boolean.TRUE);

boolean b1 = bitSet.get(0);

boolean b2 = bitSet.get(2);

查看BitSet.class的源码,可以发现,内部是使用long数组作为存储的。

每个long元素,可以表示8字节,也就是64位二进制数字。

3b5dced9132fa4c9d491971797d946d3.png

每次set时,根据下标,确定到long数组的位置,先判断是否需要扩容。

然后将定位到的long数字,根据位运算,将指定位,置为0或者1。

a279ac8a4745ca1673e6cb2b47c3e549.png

每次get时,同样是根据下标,确定到long数组的位置,利用位运算,取出对应位的值即可。

b7161855f1b8d72d961b4303793c80f0.png

使用long类型作为数组存储的话,虽然并不能降低空间消耗,但是在对BitMap做集合操作时,可以降低数组的循环次数。

BitMap的未来

BitMap的功能无疑是很强大的,相较于直接存储数字,空间占用率会成数十倍的降低。

但是在现有互联网海量数据的背景下,十亿的数据,可能很轻易就会达到且超过了。

如果数据量达到百亿,甚至千亿的级别下,那又将达到单机处理能力的瓶颈。

这里会有几种优化方向:

BitMap压缩技术

如果将BitMap保存到外部存储(数据库或者文件),计算时从外部存储加载到内存,这种情况下,存储的BitMap越大,需要的外部存储空间就越大;并且IO消耗也会更大。

这时,可以选择用gzip等文件压缩技术,在存储时,将BitMap文件进行压缩,在加载时再进行解压。

可以降低存储空间和IO消耗,但是代价是压缩和解压缩时,会消耗更多的CPU资源。

其次,对BitMap自身,也是有一些压缩技术的。

BitMap存储的,实际上是一些连续的0集合和1集合。那么我们是可以对于大段的0集合和1集合,进行合并。

例如,对于00000000111111111111000011111而言,可以存储为0,8,1,12,0,4,1,6。

也就是,8个0,12个1,4个0,6个1。

在海量存储里面,可能会有极大段的连续相同数据,用该压缩技术,可以极大降低BitMap自身的占用空间。

分布式计算

在使用BitMap做集合操作时,假如BitMap的数据特别巨大,比如一年的打卡数据,甚至更多,那么单机的计算能力和存储能力,就完全成为瓶颈。

这时,其实是可以引入分布式计算的。

分布式计算的理论基础:

在集合代数里面,集合的交、并、差、补操作,是满足交换律、分配率和结合律的。详见集合代数

6368b020a81ce535b735f1be2a3c7d2d.png

基于这些集合代数计算的原理,我们可以把复杂的多维交叉分析,分解成很多小单元计算,分配到不同的服务器上计算,再做汇总计算得到结果。

总结

BitMap在某些应用领域,使用起来会很强大。但是也会有一些限制弊端,需要我们去考虑到。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值