简单谈谈BitMap

107 阅读4分钟

简单谈谈BitMap

前言

对比——bitmap的使用与否

在咱们之前的储存结构里,需要O(1)复杂度进行查找时,我们一般习惯于用HashMap或者HashSet

如果我们想要储存一个int类型的数据,那么一个数据需要占据4个字节

若是想存储一个long类型的数据,那么一个数据需要占据8个字节

当数据非常大时,使用HashMap的情况下,内存可能撑不住

假设当有10亿个long类型的数据需要去重或者查询某个数据是否在其中时

这些数据一共需要 1000000000*8 个字节的数据,也就是**7629MB**,即约**7.45个G**的内存

如果我们将每一个数据都使用一个bit去储存呢?

10亿bit就是1000000000/8个字节的数据,也就是**119MB,即约0.12G**的内存

可以显著地看见,同样的数据量的情况下,使用bit存储比字节存储节省了约64倍的空间

为什么能节省这么多倍的空间呢?

因为long类型占8个字节,1个字节为8个bit,所以一个long类型占用64个bit

使用bit后,一个long数据只用1个bit存储,所以节省了64倍

复习——位运算符

&与运算

当上下位都为1时才为1,有任何不同则为0

1 1 1 0 0 1

&

0 1 0 0 1 0

=

0 1 0 0 0 0

|或运算

上下位存在至少一个1则为1,都为0时才为0

1 1 1 0 0 1

&

0 1 0 0 1 0

=

1 1 1 0 1 1

~非运算

非运算即取反运算,在二进制中1变0,0变1

~ 1 1 1 0 0 1

=

0 0 0 1 1 0

bitmap简介

下文的图片和文字来源于www.cnblogs.com/cjsblog/p/1…

Bit-map的基本思想就是用一个bit位来标记某个元素对应的Value,而Key即是该元素。

由于采用了Bit为单位来存储数据,因此非常节省存储空间

Bitmap主要用于快速检索关键字状态

如何用bitmap表示一个数呢?

每一位表示一个数,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]的第5个位置,那我们把1向左移动5位,然后按位或

换成二进制就是

img

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

移除元素

假设我们要6移除,该怎么做呢

只需将该数所在的位置为0即可

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

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

查找元素是否存在

前面我们也说了,每一位代表一个数字,1表示有(或者说存在),0表示无(或者说不存在)。

通过把该为置为1或者0来达到添加和清除的效果,那么判断一个数存不存在就是判断该数所在的位是0还是1

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

代码实现

public class MyBitSetTest {

    public static void main(String[] args) {
        MyBitSet myBitSet=new MyBitSet();

        long now1 = System.currentTimeMillis();
        for (int i = 1; i <= 2000000000; i++) {
            myBitSet.add(i);
        }
        System.out.println("myBitSet.exists(150) = " + myBitSet.exists(150));
        System.out.println("myBitSet.exists(400) = " + myBitSet.exists(400));

        System.out.println("myBitSet.remove(150) = " + myBitSet.remove(150));
        System.out.println("myBitSet.exists(150) = " + myBitSet.exists(150));
        System.out.println("myBitSet.remove(2000) = " + myBitSet.remove(2000));
        System.out.println("myBitSet.exists(2000) = " + myBitSet.exists(2000));
        long now2 = System.currentTimeMillis();
        System.out.println("now2 - now1 = "+(now2-now1)/1000 +"秒处理完成");

    }

    private static class MyBitSet {
        //每个值有64位,即64个数据
        private int bigNum=64;
        //64是2的6次方,num>>er 表示num/64
        private int er = 6;
        //初始化大小为两个数组,可以放128个数据后再扩容
        private int size;
        //扩容大小,默认一倍
        private int cap;
        //long型8字节,64位,需要和bigNum对应
        private long[] array;

        public MyBitSet() {
            this.size=2;
            this.cap=1;
            this.array=new long[size];
        }
        public MyBitSet(int size) {
            if(size<1)throw new IllegalArgumentException("The 'size' can not be less than 1");
            this.size = size;
            this.cap=1;
            this.array=new long[size];
        }
        public MyBitSet(int size, int cap) {
            if(size<1||cap<1)throw new IllegalArgumentException("The 'size' or 'cap' can not be less than 1");
            this.size = size;
            this.cap = cap;
            this.array=new long[size];
        }

        public boolean add(long num){
            if(num<0)return false;
            int arrayIndex = getArrayIndex(num);
            if(arrayIndex>=size){
                resize();
            }
            int itemIndex = getItemIndex(num);
            array[arrayIndex] = (array[arrayIndex] | (1<<itemIndex));
            return true;
        }
        public boolean remove(long num){
            int arrayIndex = getArrayIndex(num);
            if(num<0||arrayIndex>=size)return false;
            int itemIndex = getItemIndex(num);
            array[arrayIndex] = array[arrayIndex] & ~(1<<itemIndex);
            return true;
        }

        public boolean exists(long num){
            int arrayIndex = getArrayIndex(num);
            if(num<0||arrayIndex>=size)return false;
            int itemIndex = getItemIndex(num);
            return (array[arrayIndex] & (1<<itemIndex)) != 0;
        }

        private int getArrayIndex(long num){
            return (int)num >> er;
        }
        private int getItemIndex(long num){
            return (int)num%bigNum;
        }
        private void resize(){
            size<<=cap;
            long[] newArray=new long[size];
            System.arraycopy(array, 0, newArray, 0, array.length);
            array=newArray;
        }
    }
}

结果

myBitSet.exists(150) = true myBitSet.exists(400) = true myBitSet.remove(150) = true myBitSet.exists(150) = false myBitSet.remove(2000) = true myBitSet.exists(2000) = false now2 - now1 = 9秒处理完成