Bit-map算法实现

需求:

用户标签包括用户的社会属性、生活习惯、消费行为等信息,例如:

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

  1. 可进行数据的快速查找,判重,删除,一般来说数据范围是int的10倍以下。
  2. 去重数据而达到压缩数据

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

 

posted on 2019-09-22 09:51  自律的蚂蚁  阅读(459)  评论(0)    收藏  举报

导航