JAVA实现位图
概述
JAVA中一个int占4字节,32位二进制数, 如果一个int整数32位二进制都表示一个数的话,那么一个整数能表示[0,31]
如果想表示[32,64]可以加入数组,让索引0位置的数表示[0,31],索引1表示[32,64],以此类推只要知道要存入的最大值,就可以实现一个比原来数组占用空间至少小32倍的数组 ,就是位图
位图的实现
1 . 初始化数组大小
假设N就是位图中的最大值,那么数组的长度就应该是N / 32 + 1
等同于 (N + 1) >> 5
2 . 加入元素
a . N >> 5 找到索引值
b . N % 32 余数就是索引值所在32位的具体位置,等同于N & 31
c . 让1 左移N &31位和数组索引位置值得二进制做个 | 运算
可能描述起来还不是特别清楚,直接上图吧!!
3 . 删除元素
删除元素和增加元素类似,在处理结果时让索引位置的值 & ~(1 << (N & 31))
就把这个位置上设置成0了
4 . 判断元素是否存在
这个是和add差不多的操作,查看N的索引N的位置的这个值是否为1
直接 (arr[val >> 5] & (1 << (val & 63))) != 0 即可
代码如下(带对数器)
package bitmap;
import java.util.HashSet;
/**
* @Describe 实现位图
* @Author 王超
* @Version V1.0.0
* @Date 2021/11/3 11:10
*/
public class BitMap {
/**
* 定义一个int数组,一个整数能表示32个数
*/
private int[] arr;
public BitMap(int max) {
// 0 ~31
// 32 ~ 63
// 64 ~ 127
// max / 32 + 1 = 数组长度
arr = new int[(max + 32) >> 5];
}
/**
* 1 . val >> 5 是因为找到索引位置 0~31是第0个索引位置,32~63是第一个索引位置 ,, 所以 /32 就是索引位置
* 2 . 索引位置找到了,就应该找当前数在当前索引上处于32位的第几位就 = val & 63 = val % 64
* 3 . 1 << 是为了把这个二进制为无论0 1 都变成1
* 4 . arr |= 是因为 ==> 如果之前这个索引位置上32位某一位被标记成1了,再来个数也在这个索引上,不仅这个数的32位对应的位要标称1还要不能改变之前的32位标记情况. 所以用|运算 arr[val << 5] |= 1 << (val & 63);
* 例子 :0000 0000 0000 0000 0000 0000 0000 0000
* add 个 0 变成 0000 0000 0000 0000 0000 0000 0000 0001
* add 个 1 变成 0000 0000 0000 0000 0000 0000 0000 0011
* add 个 2 变成 0000 0000 0000 0000 0000 0000 0000 0111
* 例子过程 :
* 1 . 加入0 ==> 先找到索引位置 == > 0 / 32 == 0
* 2 . 锁定自己在32位的哪一位 == > 0 % 32 == 0
* 3 . 把自己所在位标记成1 == > 000 ... 0001(1) << 0(val所在32位的位置) 得到 0000 0000 0000 0000 0000 0000 0000 0001
* 4 . 0000 0000 0000 0000 0000 0000 0000 0001 | 0000 0000 0000 0000 0000 0000 0000 0000 = 0000 0000 0000 0000 0000 0000 0000 0001
* 5 . 加入0 ==> 先找到索引位置 == > 1 / 32 == 0
* 6 . 锁定自己在32位的哪一位 == > 1 % 32 == 1
* 7 . 把自己所在位标记成1 == > 000 ... 0001(1) << 1(val所在32位的位置) 得到 0000 0000 0000 0000 0000 0000 0000 0010
* 8 . 0000 0000 0000 0000 0000 0000 0000 0010 | 0000 0000 0000 0000 0000 0000 0000 0001 = 0000 0000 0000 0000 0000 0000 0000 0011
*
* @param val val
*/
public void add(int val) {
arr[val >> 5] |= 1 << (val & 63);
}
/**
* 1 . val >> 5 是因为找到索引位置 0~31是第0个索引位置,32~63是第一个索引位置 ,, 所以 /32 就是索引位置
* 2 . 索引位置找到了,就应该找当前数在当前索引上处于32位的第几位就 = val & 63 = val % 64
* 3 . 1 << 是为了把这个二进制为无论0 1 都变成1
* 4 . ~取反是为了只把删除这个位变成0,其他位都是1
* 4 . arr &= 是因为 ==> 原来位上可能有很多1 & 只有删除位是0其他位都是1 = 其他位是1还是1只有删除位变成0了
* 例子 :0000 0000 0000 0000 0000 0000 0000 0000
* add 个 0 变成 0000 0000 0000 0000 0000 0000 0000 0001
* add 个 1 变成 0000 0000 0000 0000 0000 0000 0000 0011
* add 个 2 变成 0000 0000 0000 0000 0000 0000 0000 0111
* 例子过程 :
* 1 . 加入0 ==> 先找到索引位置 == > 0 / 32 == 0
* 2 . 锁定自己在32位的哪一位 == > 0 % 32 == 0
* 3 . 把自己所在位标记成1 == > 000 ... 0001(1) << 0(val所在32位的位置) 得到 0000 0000 0000 0000 0000 0000 0000 0001
* 4 . 0000 0000 0000 0000 0000 0000 0000 0001 | 0000 0000 0000 0000 0000 0000 0000 0000 = 0000 0000 0000 0000 0000 0000 0000 0001
* 5 . 加入0 ==> 先找到索引位置 == > 1 / 32 == 0
* 6 . 锁定自己在32位的哪一位 == > 1 % 32 == 1
* 7 . 把自己所在位标记成1 == > 000 ... 0001(1) << 1(val所在32位的位置) 得到 0000 0000 0000 0000 0000 0000 0000 0010
* 8 . 0000 0000 0000 0000 0000 0000 0000 0010 | 0000 0000 0000 0000 0000 0000 0000 0001 = 0000 0000 0000 0000 0000 0000 0000 0011
*
* @param val val
*/
public void delete(int val) {
// 找到他把他改成0
arr[val >> 5] &= ~(1 << (val & 31));
}
/**
* 判断val是够存在位图中
* 思路是: 1 << 到val的位上,用索引的这个值做 & 运算,这样除了val位上是1,其他位置全变成0了
* 如果1这个位对应的也是1就返回非0数,否则返回0
*
* @param val val
* @return Boolean
*/
public boolean contains(int val) {
return (arr[val >> 5] & (1 << (val & 63))) != 0;
}
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("异常!");
break;
}
}
}
for (int num = 0; num <= max; num++) {
if (bitMap.contains(num) != set.contains(num)) {
System.out.println("异常!");
}
}
System.out.println("测试结束!");
}
}