RoaringBitmap分析及使用

1.bitmap

bitmap是大数据处理中常用的一种数据结构,可以用来做去重与基数统计。
考虑如下场景:
有1千万个整数,整数的范围在1到1亿之间。如何快速判断某个整数是否在这1千万个整数中?

如果用散列这种常见的数据结构,内存的占用肯定是非常大,基本不可用。
如果开辟一个长度为1亿的整形数组,一个int型占用4个字节,最后算下来内存占用也很大。
有的同学说可以将数组换成布尔数组,可以减小内存占用。对比整形,布尔型内存占用确实较小,但是很多语言中布尔型变量大小是1个字节,内存占用还是相当可观。

实际上,表示true/false只需要一个bit就够了,一个字节有8个bit,将布尔数组换成二进制,就是我们的bitmap,可以将内存空间再压缩到原来的1/8。

2.RoaringBitmap

bitmap的问题在于,不管业务中实际的元素基数有多少,它占用的内存空间都恒定不变。还是以上面的例子来说明,整数的范围仍然是1到1亿之间,但是数字只有10000个,需要判断某个数字在不在这10000个数字中?

如果还是使用原始的bitmap结构,事先需要开辟的内存空间还是1亿个bit,但是只有1w个bit位是1,其余都是0。数据越稀疏,空间浪费越严重。

为了解决位图稀疏存储浪费空间的问题,出现了很多稀疏位图的压缩算法,RoaringBitmap就是其中的优秀代表。

RoaringBitmap的主要思路如下:
将32位无符号整数按照高16位分桶,即最多可能有216=65536个桶,论文内称为container。存储数据时,按照数据的高16位找到container(找不到就会新建一个),再将低16位放入container中。也就是说,一个RoaringBitmap就是很多container的集合。

论文中的原图如下
在这里插入图片描述

图中示出了三个container:
第一个container是高16位为0000H,container中基数为1000,具体数值为1000个62的倍数。
第二个container是高16位为0001H,container中基数为100,具体数值为[2^16, 2^16+100)
第三个container是高16位为0001H,container中基数为2^15,存储有[2×2^16, 3×2^16)区间内的所有偶数

3.container种类

在roaringbitmap中主要有以下几种小桶:arraycontainer(数组容器),bitmapcontainer(位图容器),runcontainer(行程步长容器)

ArrayContainer

在创建一个新container时,如果只插入一个元素,roaringbitmap默认会用ArrayContainer来存储。当ArrayContainer的容量超过4096,即8k后,会自动转成BitmapContainer(这个所占空间始终都是8k)存储。

bitmapcontainer

这个容器就是第一部分讲的普通位图,只不过这里位图的位数为2^16=65536个,也就是66536个bit。计算下来起所占内存就是8kb。然后每一位用0,1表示这个数不存在或者存在。

runcontainer

它使用可变长度的unsigned short数组存储用行程长度编码(RLE)压缩后的数据。举个例子,连续的整数序列11, 12, 13, 14, 15, 27, 28, 29会被RLE压缩为两个二元组11, 4, 27, 2,表示11后面紧跟着4个连续递增的值,27后面跟着2个连续递增的值。

4.在java中的实现与调用

java中已有相关的类库实现了RoaringBitmap相关功能,使用前我们引入如下依赖:

        <dependency>
            <groupId>org.roaringbitmap</groupId>
            <artifactId>RoaringBitmap</artifactId>
            <version>0.9.10</version>
        </dependency>

并使用如下代码测试

import org.roaringbitmap.RoaringBitmap;

public class RoaringBitMapDemo {

    public static void t1() {
        RoaringBitmap rr = RoaringBitmap.bitmapOf(1, 2, 3, 1000);
        RoaringBitmap rr2 = new RoaringBitmap();
        rr2.add(4000L, 4005L);
        // 第三个数值,索引从0开始
        int thirdvalue = rr.select(3);
        // 2这个值的排序,排序索引从1开始,如果不在是0
        int indexoftwo = rr.rank(2);
        boolean c1 = rr.contains(1000);
        boolean c2 = rr.contains(7);

        System.out.println("bofore or, rr is: " + rr);
        System.out.println("thirdvalue is: " + thirdvalue);
        System.out.println("indexoftwo is: " + indexoftwo);
        System.out.println("c1 is: " + c1);
        System.out.println("c2 is: " + c2);
        System.out.println();

        // 做并集
        RoaringBitmap rror = RoaringBitmap.or(rr, rr2);
        rr.or(rr2);

        System.out.println("rr is: " + rr);
        System.out.println("rr2 is: " + rr2);
        System.out.println("rror is: " + rror);

        boolean equals = rror.equals(rr);
        System.out.println("is equals: " + equals);

        // 获取位图中元素个数
        long cardinality = rr.getLongCardinality();
        System.out.println("cardinality is: " + cardinality);
    }

    public static void main(String[] args) {
        t1();
    }
}

最后输出的结果

bofore or, rr is: {1,2,3,1000}
thirdvalue is: 1000
indexoftwo is: 2
c1 is: true
c2 is: false

rr is: {1,2,3,1000,4000,4001,4002,4003,4004}
rr2 is: {4000,4001,4002,4003,4004}
rror is: {1,2,3,1000,4000,4001,4002,4003,4004}
is equals: true
cardinality is: 9

5.总结

1.原始bitmap适用于数据分布比较稠密场景,RoaringBitMap适用于数据分布比较稀疏的场景。
2.roaringbitmap做交并差的速度也比原始的bitmap快,主要原因是roaringbitmap将原始大块的bitmap分成了各个小块,数据分布稀疏的情况下小块container数量较少,大大减少了需要处理的bitmap大小。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值