BitMap核心算法详解及实现

什么是Bitmap

所谓的BitMap就是用一个bit位来标记某个元素所对应的value,而key即是该元素,由于BitMap使用了bit位来存储数据,因此可以大大节省存储空间。

重要特性

1.存储空间小

由于Bitmap是通过bit来标识状态,数据将会高度压缩因此占用存储空间极小,存储变小也可以带来很多其它性能红利,如:内存、磁盘IO、网络带宽等。

如果用int类型表示一个整数,占用空间为(4byte = 32bit)。若用bitmap表示一个整数,占用空间为1bit。 内存节省了32倍。假设需要对10亿个整数进行排序(10亿个数据不重复,且范围在0 ~ 2^32)

如果用int类型表示需要占用空间:3.72G 左右

如果用bitmap类型表示需要占用空间:510MB 左右

如果bitmap加上对应的压缩算法roaringbitmap: 360MB 左右

2.计算速度快

由于BitMap是通过bit位来表示的,因此后面的计算都是基于位运算的,速度都会得到大幅度提升。

基本思想

我们用一个具体例子来说明Bitmap的原理。假设我们要对0-7内的8个元素中的(6,2,7,1)四个元素进行排序(这里假设元素没有重复)。我们可以使用Bitmap算法达到排序目的。

1.开辟1 Byte空间(要表示8个数,我们需要8个bit(1Byte)),将这些空间所有的bit都设置为0,如下图:

2.然后依次将四个元素根据Value的值将对应的bit位置设置为 1,最终8个bit位状态如下图:

3.现在我们遍历一次,把值为1的bit的位置输出(1,2,6,7),这样便达到了排序的目的。

从上面的例子我们可以看出,BitMap算法的思想还是比较简单的,关键的问题是如何确定10进制的数到2进制的映射图,接下来我们研究一下map映射。

注意:由于BitMap是用角标来记录对应的值,而角标是从0开始计数的,因此普通的BitMap只适用于非负数的排序或者去重操作。

MAP映射

BitMap中1bit代表一个数字,1个int = 4Bytes = 4*8bit = 32 bit,假设现有N个非负整数,取值范围[0,N-1],那么N个数需要N/32 int空间,其中:a[0]在内存中占32为可以对应十进制数0-31,依次类推如下图:

a[0]--------->[0]~[31]  ->bit表示[0000000000000000000000000000000000000]
a[1]--------->[32]~[63] ->bit表示[0000000000000000000000000000000000000]
a[2]--------->[64]~[95] ->bit表示[0000000000000000000000000000000000000]
.
.
.
.
.
a[n]--------->[N-32]~[N-1]

说明:n = (N/32) - 1

注意:这里需要提醒一下,a数组的size是由待排序数中的最大值决定,而不是待排序数个数决定的。例如:待排序数组是[3,1,35],这个数组只有三个数字,但是在开辟BitMap空间时应该根据最大值35,因此需要开辟2个int空间,这个地方大家可以思考一下。自己在学习过程中,发现网络上多篇文章在这个地方都是错误的,因此特意提醒!

接下来介绍如何用位运算将十进制数转换为对应的bit位:

1.求十进制数在对应数组a中的下标

十进制数0-31,对应在数组a[0]中,32-63对应在数组a[1]中,64-95对应在数组a[2]中………,使用数学归纳分析得出结论:对于一个十进制数n,其在数组a中的下标为:a[n/32]

2.求出十进制数在对应数a[i]中bit位的下标

例如十进制数1在a[0]的bit位下标为1,十进制数31在a[0]的bit位下标为31,十进制数32在a[1]的bit位下标为0。十进制0-31就对应0-31,而32-63则对应也是0-31,即给定一个数n可以通过模32求得在对应数组a[i]中的bit位下标。

3.位移运算

对于一个十进制数n,对应在数组a[n/32][n%32]中,但数组a毕竟不是一个二维数组,我们通过移位操作实现将对应的bit位置 1。

位运算公式:a[n/32] |= 1 << (n % 32),即 a[n/32] = a[n/32] |(1 << (n % 32))

公式还有一个可以优化的地方,n % 32 等价于 n & 0x1F,解释:(n & 0x1F) 保留n的后五位 相当于 n % 32

至此,整个MAP映射过程结束。需要注意的是,本示例中底层使用的存储是int[],如果换做其它类型数组,如:long,则对应公式需要做相应调整。

代码实现

根据上面介绍的算法原理,代码实现如下:

package com.zhouj.endless.ds;

import java.util.ArrayList;
import java.util.List;

/**
 * @author zhouj
 * @date 2021-07-20 22:28
 * @desc BitMap实现
 */
public class BitMap {
    // 最大值
    private static final int N = Integer.MAX_VALUE;
    // [0,2147483647]
    private int[] a = new int[N / 32 + 1];

    /**
     * 设置bit位为1
     *
     * @param n
     */
    public void setBit(int n) {
        //row = n / 32 求十进制数在数组a中的下标
        int row = n >> 5;
        //相当于 n % 32 求十进制数在数组a[i]中的下标
        a[row] |= 1 << (n & 0x1F);
    }

    // 判断所在的bit为是否为1
    public boolean exits(int n) {
        int row = n >> 5;
        return (a[row] & (1 << (n & 0x1F))) != 0;
    }

    /**
     * 展示前row个数组
     *
     * @param row
     */
    public void display(int row) {
        System.out.println("BitMap位图展示前N组");
        for (int i = 0; i < row; i++) {
            List<Integer> list = new ArrayList<>();
            int temp = a[i];
            for (int j = 0; j < 32; j++) {
                list.add(temp & 1);
                temp >>= 1;
            }
            System.out.println("a[" + i + "]--------->" + 32 * i + "~" + (32 * (i + 1) - 1) + "->bit表示" + list);

        }
    }

    /**
     * 展示第N个数组
     *
     * @param n
     */
    public void displayN(int n) {
        System.out.println("BitMap位图展示第N组");
        List<Integer> list = new ArrayList<>();
        int temp = a[n - 1];
        for (int j = 0; j < 32; j++) {
            list.add(temp & 1);
            temp >>= 1;
        }
        System.out.println("a[" + (n - 1) + "]--------->" + 32 * (n - 1) + "~" + (32 * n - 1) + "->bit表示" + list);


    }

    public static void main(String[] args) {
        int num[] = {1, 5, 30, 31, 64, 56, 159, 120, 21, 17, 35, 45, Integer.MAX_VALUE};
        BitMap map = new BitMap();
        for (int i = 0; i < num.length; i++) {
            map.setBit(num[i]);
        }
        map.display(6);

        int temp = Integer.MAX_VALUE;
        if (map.exits(temp)) {
            System.out.println("temp:" + temp + " exists");
        }
        map.displayN(Integer.MAX_VALUE / 32 + 1);
    }
}

运行结果如下:

本文给出的只是BitMap原来简单实现,在实际应用中有更复杂的情况。JDK中,是直接提供的BitMap集合的实现类的:java.util.BitSet,有兴趣的可以去研究一下。另外介于篇幅原因,针对BitMap的应用将在下一篇文章进行介绍和分析。

感谢阅读!

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值