位图是什么?
介绍位图之前先来看看集合。我们平时开发中经常会用到各种各样的集合,集合支持往里面放数据,查看是否包含某个元素等。
比如说HashSet,是一个不可有重复元素的集合,此时如果用HashSet来存放数据,1,4,6三个数据,那int整数类型每个都是32位的4Byte大小,占用着12Byte大小。那如果这个数字是有范围的话,是否可以省略一些空间?
比如在范围在0~31,如果正常用集合来存,就是32个数, 占用32 * 4 = 128Byte,那如果用位图来存呢? 只需要4个Byte即可。
如何使用位图来实现?
只需要用一个int类型整数来存即可,因为每个int类型都是4字节大小,1字节 = 8 bit,4字节就是32bit,那如果用每一位来代表0~31范围之间内的数,不就可以用1个数就解决了0 ~ 31之间数的存储么?
举例:
0: 00000000 00000000 00000000 00000000 正常用二进制表示0,就是32个0,那如果每一位都代表着一个数,32位正好可以用来存储0 ~ 31。
如果用来存储1,那么只需要改变1位置的0 为 1即可,同理,如果将5加入里面,那么5位置的数字也变成1 即可。
那如果范围大于31呢? 比如 0 ~ 128
那么用一个Int[]即可,数组多长呢? 数组中每个元素都可以存储0 ~ 31个数,那用数组的0位置存储0 ~ 31,1位置存储 32 ~ 63依次类推,那么只需要4长度的int[]即可。
位图的功能
最常见的就是redis中的布隆过滤器(判断一个元素是否在集合中),ES底层的RBM压缩算法。
这其中都有着位图的思想在里面。
位图的好处
压缩空间!!!!
位图的实现
如何实现位图
创建一个BitMap类
首先,创建一个类,并且类用有一个long[]类型成员变量(int[]也可),并在构造器中赋值长度。
我这里是用了一个静态内部类。
为什么要 >> 6? 因为如果max是0或者<64的数,也是需要1长度的数组来存储。
public static class BitMap {
long[] bit;
public BitMap(int max) {
// (max + 64) >> 6 -> (max + 64) / 64
bit = new long[(max + 64) >> 6];
}
}
添加元素
因为是long类型数组,所以每个元素可存储0 ~ 63个数
首先,要计算添加的元素要存储在数组下标为几的地方(bit[num >> 6] )。
其次,要计算元素在索引处的值的二进制位中的位置(num & 63 == num % 64)。
为什么 num & 63 == num % 64? num % 64的范围在 0 ~ 63之间
假设 num的二进制为 00000000 00000000 00000010 10010111
64的二进制为 00000000 00000000 00000000 01000000
那么 num % 64的结果,就是只剩下二进制的后7位0010111 ,
而&运算,都为1才为1,所以结果与% 64结果相同,但&运算速度很快。
上一步操作已经找到要存放的位置,再用| 操作,将1 左移 通过| 操作赋值
public void add(int num) {
bit[num >> 6] |= (1L << (num & 63));
}
删除元素
删除元素是将该位置置0,所以还是找到所在位置,取反后,该位置为0。
public void delete(int num) {
bit[num >> 6] &= ~(1L << (num & 63));
}
包含元素
public boolean contains(int num) {
return (bit[num >> 6] & (1L << (num & 63))) != 0;
}