Bit-map算法实现
需求:
用户标签包括用户的社会属性、生活习惯、消费行为等信息,例如:
- 用户A: 男,程序员,90后,华为
- 用户B: 男,程序员,80后,苹果
- 用户C: 女,企业家,70后,三星
- 用户D: 男,程序员,90后,苹果
现在,想要统计所有男性程序员。一种方法是通过求交集的SQL实现。但如果标签非常多,甚至达到上千个,那么数据库表就需要添加很多列,且查询SQL也会变得非常长,还要去掉重复的数据,可见性能会非常差。
Bit-map就是用一个bit位来标记某个元素对应的Value, 而Key即是该元素。由于采用了Bit为单位来存储数据,因此在存储空间方面,可以大大节省。
此时,统计可以做如下转变:
- 建立用户和id的关系,用户A:1,用户B:2,用户C:3,用户D:4
- 让将每个标签使用bitmap来存储用户,比如:

使用苹果手机且为90后的用户: 000010010B & 000010010B = 00001000B,所以为id=4的用户
看完下面的几个小节的算法详细介绍后,再来看这段:
数组里的元素类型使用byte表示,即数组的每一个元素都是8bit的二进制(00000000B)。那么存储数据为4(4个用户)的bitmapArr所需开辟的数组空间:4/8 +1 = 1,所以bitmapArr的长度=1即可满足要求。即bitmapArr = new byte[1],且数组的第一位存的是8bit的二进制的数,即bitmapArr[0]=00000000。
- 用户1:index:1/8 = 0,position:1%8=1,所以用户1在bitmapArr中位置为:bitmapArr[0],00000001
- 用户2: bitmapArr[0],00000010
- 用户3: bitmapArr[0],00000011
- 用户4: bitmapArr[0],00000100
1. Bit-map应用
- 可进行数据的快速查找,判重,删除,一般来说数据范围是int的10倍以下。
- 去重数据而达到压缩数据
2. Bit-map算法评价
优点:
1. 运算效率高,不进行比较和移位;
2. 占用内存少,比如最大的数MAX=10000000;只需占用内存为MAX/8=1250000Byte=1.25M。
缺点:
1. 所有的数据不能重复,即不可对重复的数据进行排序。(少量重复数据查找还是可以的,用2-bitmap)。
2. 当数据类似(1,1000,10万)只有3个数据的时候,用bitmap时间复杂度和空间复杂度相当大,只有当数据比较密集时才有优势。
3. Bit-map算法示例
3.1 只需要1个8bit空间的例子
假设要对0-7内的5个元素(4,7,2,5,3)排序(这里假设这些元素没有重复)。那么就可以采用Bit-map的方法来达到排序的目的。
用8个Bit(1Bytes)来表示0-7内的8个数,并将这些空间的所有Bit位都置为0(如下图):
然后遍历这5个元素,首先第一个元素是4,那么就把4对应的位置为1(可以这样操作 p+(i/8)|(0×01<<(i%8)) 当然了这里的操作涉及到Big-ending和Little-ending的情况,这里默认为Big-ending。不过计算机一般是小端存储的,如intel。小端的话就是将倒数第5位置1),因为是从零开始的,所以要把第五位置为一(如下图):
然后再处理第二个元素7,将第八位置为1,,接着再处理第三个元素,一直到最后处理完所有的元素,将相应的位置为1,这时候的内存的Bit位的状态如下:

最后遍历一遍Bit区域,将位置是1的对应的编号输出(2,3,4,5,7),这样就达到了排序的目的。
3.2 需要多个8bit空间的例子
假设需要排序或者查找的最大数MAX=10000000(这里MAX应该是最大的数而不是int数据的总数!),那么我们需要申请内存空间的大小为int a[1 + MAX/32],在内存中int占4个字节共32位。其中:a[0]在内存中占32为可以对应十进制数0-31,依次类推:
bitmap表为:
a[0]--------->0-31
a[1]--------->32-63
a[2]--------->64-95
a[3]--------->96-127
..........
要把一个整数N映射到Bit-Map中去,首先要确定把这个N Mapping到哪一个数组元素中去,即确定映射元素的index。我们用int类型的数组作为map的元素,这样我们就知道了一个元素能够表示的数字个数(这里是32)。于是N/32就可以知道我们需要映射的key了。所以余下来的那个N%32就是要映射到的位数。比如:N=65,65/32=2(-->对应在数组a中的下标为1,a[1]),65%32=1(--> 对应0-31的第1位),那么65的位置应该在a[1]的0-31中的第1位置。
4. 移位操作的实现
4.1 移位实现:求十进制数对应在数组a中的下标i
i = n>>K 等同于 n/(2^K)
int型数组,数组的每个元素都是int,int需要32bit的空间。所以先由十进制数n转换为与32的余可转化为对应在数组a中的下标。如十进制数0-31,都应该对应在a[0]中,比如n=24,那么 n/32=0,则24对应在数组a中的下标为0。又比如n=60,那么n/32=1,则60对应在数组a中的下标为1,同理可以计算0-N在数组a中的下标。如果要求n在int型数组中的下标,那么就需要n/32,所以上面公式K=5。
Note: map的范围是[0, 原数组最大的数对应的2的整次方数-1]。
4.2 移位实现:求十进制数在数组元素a[i]中0-31的位置m
m = n & ((1 << K) - 1) 等同于 n%(2^K)
十进制数0-31就对应0-31,而32-63则对应也是0-31,即给定一个数n可以通过模32求得对应0-31中的数。如果要求n在a[i]中的0-31的具体位置,那么就需要n%32,所以上面公式K=5。
4.3 移位实现:设置int型a[i]的0-31中第m位的bit位为1
a[i] = a[i] | (1<<m)
利用移位0-31使得对应第m个bit位为1。如:将当前4对应的bit位置1的话,只需要1左移4位与B[0] | 即可。
Note: 1 p+(i/8)|(1<<(i%8))这样也可以?
4.4 移位实现:设置int型a[i]的0-31中第k位的bit位为0
a[i] = a[i] & ~(1<<k)
5. Bit-map算法代码实现
1 package blogSrc; 2 3 public class BitMap { 4 private byte[] bitMapArr; //用来保存数据的数组,每一个元素存放的是byte型,对应一个8位二进制数据。如果int型,就是32位... 5 private int capacity; //待存储的数据量大小,根据这个值来决定设定多大的bitArr 6 7 //初始化BitMap 8 public BitMap(int capacity){ 9 this.capacity = capacity; 10 //数组每个元素(byte型)能存储8个数据,那么capacity数据需要多少个bit呢,capacity/8+1,右移3位相当于除以8 11 bitMapArr = new byte[(capacity >> 3)+1]; 12 } 13 14 //将数据添加到bitMapArr中 15 public void add(int num){ 16 //首先求num应该在数组bitMapArr中的index,num/8得到byte[]的index 17 int index = num >> 3; 18 //求num应该在的位置,num%8得到在byte[index]的位置 19 int position = num & 0x07; 20 21 //将1左移position位,然后和原来的数组数据做或运算(|),就可以将positon位置上的数置为1 22 bitMapArr[index] |= 1 << position; 23 } 24 25 //清除bitMapArr中当前num位置的值,相当于从bitMapArr中删除当前num 26 public void clear(int num){ 27 //首先求num应该在数组bitMapArr中的index,num/8得到byte[]的index 28 int index = num >> 3; 29 //求num应该在的位置,num%8得到在byte[index]的位置 30 int position = num & 0x07; 31 //将1左移position位,然后取反,再与当前值做&,即可清除当前的位置了. 32 bitMapArr[index] &= ~(1 << position); 33 } 34 35 //判断数据是否在bitMapArr中 36 public boolean isExist(int num){ 37 //首先求num应该在数组bitMapArr中的index,num/8得到byte[]的index 38 int index = num >> 3; 39 //求num应该在的位置,num%8得到在byte[index]的位置 40 int position = num & 0x07; 41 //将1左移position位,然后和原来的数组数据做与运算(&),就可以得到positon位置上原先的值是否为1,为0说明该num还不存在bitmap中 42 int result = bitMapArr[index] & 1 << position; 43 return result != 0; 44 } 45 46 47 public static void main(String[] args){ 48 BitMap bitMap = new BitMap(100); //待存放数的个数为100,数组长度至少为12 49 System.out.println("初始化:待存放100个数,bitMapArr数组长度至少为" + bitMap.bitMapArr.length); 50 System.out.println("10是否存在:" + bitMap.isExist(10)); 51 bitMap.add(10); 52 System.out.println("添加10..." ); 53 System.out.println("10是否存在:" + bitMap.isExist(10)); 54 bitMap.clear(10); 55 System.out.println("删除10..." ); 56 System.out.println("10是否存在:" + bitMap.isExist(10)); 57 } 58 }
结果:
初始化:待存放100个数,bitMapArr数组长度至少为13 10是否存在:false 添加10... 10是否存在:true 删除10... 10是否存在:false
浙公网安备 33010602011771号