【☪海量数据】bitmap原理及在Java中的实现

Bitmap原理

Bit-map的基本思想就是用一个bit位来标记某个元素对应的Value。由于采用了Bit为单位来存储数据,因此在存储空间方面,可以大大节省。(PS:划重点 节省存储空间

假设有这样一个需求:在20亿个随机整数中找出某个数m是否存在其中,并假设32位操作系统,4G内存。

在Java中,int占4字节,1字节=8位(1 byte = 8 bit)

如果每个数字用int存储,那就是20亿个int,因而占用的空间约为  (2000000000*4/1024/1024/1024)≈7.45G

如果按位存储就不一样了,20亿个数就是20亿位,占用空间约为  (2000000000/8/1024/1024/1024)≈0.233G

那么,问题来了,如何表示一个数呢?

每一位表示一个数,0表示不存在,1表示存在,这正符合二进制。

这样我们可以很容易表示{1,2,4,6}这几个数:

计算机内存分配的最小单位是字节,也就是8位,那如果要表示{12,13,15}怎么办呢?

当然是在另一个8位上表示了:

这样的话,好像变成一个二维数组了。

1个int占32位,那么我们只需要申请一个int数组长度为 int tmp[1+N/32] 即可存储,其中N表示要存储的这些数中的最大值,于是乎:

tmp[0]:可以表示0~31

tmp[1]:可以表示32~63

tmp[2]:可以表示64~95

......

如此一来,给定任意整数M,那么M/32就得到下标,M%32就知道它在此下标的哪个位置。

添加

这里有个问题,我们怎么把一个数放进去呢?例如,想把5这个数字放进去,怎么做呢?

首先,5/32=0,5%32=5,也是说它应该在tmp[0]的第6个位置(对应下标为5),那我们把1向左移动5位,然后按位或

换成二进制就是:

这就相当于 86 | 32 = 118

86 | (1<<5) = 118

b[0] = b[0] | (1<<5)

也就是说,要想插入一个数,将1左移代表该数字的那一位,然后与原数进行按位或操作

化简一下,就是 86 + (5/8) | (1<<(5%8))

因此,公式可以概括为:p + (i/8)|(1<<(i%8)) 其中,p表示现在的值,i表示待插入的数

清除

还是上面的例子,假设我们要6移除,该怎么做呢?

从图上看,只需将该数所在的位置设置为0即可。

1左移6位,就到达6这个数字所代表的位,然后按位取反,最后与原数按位与,这样就把该位置为0了。

b[0] = b[0] & (~(1<<6))

b[0] = b[0] & (~(1<<(i%8)))

查找

前面我们也说了,每一位代表一个数字,1表示有(或者说存在),0表示无(或者说不存在)。通过把该为置为1或者0来达到添加和清除的小伙,那么判断一个数存不存在就是判断该数所在的位是0还是1

假设,我们想知道3在不在,那么只需判断 b[0] & (1<<3) 如果这个值是0,则不存在,如果是1,就表示存在。

以上添加、清除、查找功能用Java代码实现如下:

//bitmap 位图操作
public class BitMapMain {
    public static void main(String[] args) {
        //byte数组,就是除以8, int数组就是除以32
        byte[] bits = new byte[1000]; //数组大小, 根据要存的数据进行变动 n/8=1000, 即这里可以存8000个数
        byte[] arr = {0, 10, 8, 20, 33, 45, 23, 67, 102, 99};
        //初始化,保存数字
        for (int i = 0; i < arr.length; i++) {
            add(bits, arr[i]);
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.println("before clear,是否存在" + arr[i] + "值:" + contains(bits, arr[i]));
            if (arr[i] == 10 || arr[i] == 33) {
                //重置值
                clear(bits, arr[i]);
            }
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.println("after clear,是否存在" + arr[i] + "值:" + contains(bits, arr[i]));
        }
    }

    //添加
    public static void add(byte[] bits, int num) {
        bits[getIndex(num)] |= 1 << (getPosition(num));
        //10100100 -- bits[getIndex(num)]
        //00010000 -- 1左移, |或操作
        //10110100 -- bits[getIndex(num)]重新赋值
    }

    //重置
    public static void clear(byte[] bits, int num) {
        bits[getIndex(num)] &= ~(1 << getPosition(num));
        //10110100 -- bits[getIndex(num)]是添加后的新值
        //11101111 -- 1左移00010000, ~取反11101111, & 与操作
        //10100100 -- bits[getIndex(num)]
    }

    //值是否存在
    public static boolean contains(byte[] bits, int num) {
        return (bits[getIndex(num)] & 1 << getPosition(num)) != 0;
        //10110100 -- bits[getIndex(num)]是添加后的新值
        //00010000 -- 1左移,& 与操作
        //00010000 -- bits[getIndex(num)],如果有一个二进制位是1,则说明值存在
    }

    /**
     * 计算位于数组的下标, 例如14/8=1,处于bits[1]
     **/
    private static int getIndex(int num) {
        return num >> 3;
    }

    /**
     * 计算处于二进制中的第几位(得到的下标,从0开始)
     * 14%8=6, 所以下标是6, 处于第7个位置 01000000
     **/
    private static int getPosition(int num) {
        return num % 8;
    }
}

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亿个整数。 

首先,根据“内存空间不足以容纳这20亿个整数”我们可以快速的联想到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,则表示存在。

 

参考:

 

posted @ 2023-04-13 16:14  残城碎梦  阅读(988)  评论(1编辑  收藏  举报