位图
位图
1.位图的功能
2.位图的好处
3.位图的实现
例如有个集合,可以装数,3,5,-1,这个集合有add方法,有contains方法判断元素在不在,常见的有hashSet。但是这个有个问题是浪费空间的问题,比如你3进来,3这个数int类型,空间起码占4字节,4 byte,其实远远不止4字节,因为集合内部还有什么指针之类。一个数进来就4字节。
这个有点浪费了。
如果这个数是有范围的,能不能省点空间呢?
假设集合中只有一个int类型的数,int类型数,4字节,1字节= 8bit,共32位。如果集合只需要装0~31范围的数,那就可以这样:如果表示5,可以将第5位标记为1,如果表示0,可以将第0位标识为1。所以集合中这个整数是可以标识0~31之间的数是否出现过的。

如果这个集合用原来的hashSet哈希表的方式来存储需要的字节:32个数 * 4byte = 128字节。
现在只需要1个整数来表示0~31范围出现的数,只需要32bit,也就是4字节。空间节省了32倍。
但是问题是,0~31范围太小了,如果表示0~1023,怎么做?
0~1023是1024个数,前面知道一个int类型可以表示32个数,那么就需要:1024 / 32 = 32个int类型的数来表示。
采用int[]数组,长度为32。int[32]就可以表示1024个数。
int[0]:表示0~31
int[1]:表示32~63
...
int[31]: 表示xx ~ 1023

位图的功能:可以做出一个集合,如果数字范围是非常确定的,最大值很确定的话,我就可以用位图来实现收集数字并且告诉你它存不存在的功能。
位图的好处:极大的压缩空间。
位图的实现
long类型,一个long类型可以表示64个数。

代码实现:
package com.cy.class05;
import java.util.HashSet;
/**
* 位图的实现
*/
public class Code02_BitMap2 {
public static class BitMap {
private long[] bits;
/**
* (max + 64) >> 6 -> (max + 64) / 64
* 右移6位就是除64的意思
*/
public BitMap(int max) {
bits = new long[(max + 64) >> 6];
}
/**
* num >> 6 -> num / 64,定位到bit数组对应下标的整数
* num % 64 怎么写?
* num % 64 -> num & 63
*
* 这里1L不能写成1,假设加170这个数,1左移42位,系统认为1是int类型,只有32位,左移就不对了。
*/
public void add(int num) {
bits[num >> 6] |= (1L << (num & 63));
}
public void delete(int num) {
bits[num >> 6] &= ~(1L << (num & 63));
}
public boolean contains(int num) {
return (bits[num >> 6] & (1L << (num & 63))) != 0;
}
}
/**
* 测试方法
* bitMap:上面写的位图
* set:哈希表
* 以1/3的概率,两个共同加一个数
* 以1/3的概率,两个共同删一个数
* 再以1/3概率,你两共同查一个数
* 最后吧所有从0~max的数查一遍,有任何一点对应不上打印Oops
*/
public static void main(String[] args) {
System.out.println("测试开始!");
int max = 10000;
BitMap bitMap = new BitMap(max);
HashSet<Integer> set = new HashSet<>();
int testTime = 10000000;
for (int i = 0; i < testTime; i++) {
int num = (int) (Math.random() * (max + 1));
double decide = Math.random();
if (decide < 0.333) {
bitMap.add(num);
set.add(num);
} else if (decide < 0.666) {
bitMap.delete(num);
set.remove(num);
} else {
if (bitMap.contains(num) != set.contains(num)) {
System.out.println("Oops!");
break;
}
}
}
for (int num = 0; num <= max; num++) {
if (bitMap.contains(num) != set.contains(num)) {
System.out.println("Oops!");
}
}
System.out.println("测试结束!");
}
}
代码解释:
1.第12行,bits = new long[(max + 64) >> 6]; 除以64 就是 >>6

2.add方法解释:
比如要将170这个数加入到位图中,位图的存储是个数组,那么加到哪个小标? ---- 170 / 64 这个下标位置。
170到底是这个bit[i]这个整数的第几位呢? ——170 % 64

为什么num % 64 = num & 63?
代码中当然你可以写num % 64,但是模运算是比较慢的。63是全1这个玩意,111111,第5位前面的数全是0了,比如:0000000000111111
将hum和63进行&运算之后,将第5位前面的数全部搞成0了。得到第5位之后的一个数。看下图deepseek的解释吧:

public static void main(String[] args) {
int a = 0b00000000000000000000000001001100; //76
int b = 0b00000000000000000000000001000000; //64
int c = 0b00000000000000000000000000111111; //63
System.out.println(a % b); //12
System.out.println(a & c); //12 即 1100
}

继续,现在知道要将这个数的第几位标记成1了,把170放进去,就是将这个位置的数标成1就行了:
将1左移n位得到的数,与原数进行或,就将这个1或进去了。


delete方法解释:
比如要将170删除,就是说要将bit数组中170位于的下标的数,在170那个位置上标0就行了:


contain方法解释:
要查找170这个数再这个集合中存不存在,就是判断数组中存170这个数组对应小标的那个数,且在170 % 64 = 42这个位置上的数,是不是等于0,如果等于0就不存在,如果等于1就存在。

--