概述:位图法bitmap
位图法是用一个bit的0/1代表这个bit所在位置的值存在与否
注:为什么不用boolean数组?
根据《Java虚拟机规范》一书中的描述:“虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位”。
所以boolean数组,一个元素占用一个byte。
同时,根据《Java虚拟机规范》的描述,用int数组是更合适的实现,不过byte实现比int实现更容易理解,所以这里选择byte实现。
代码如下:
package bitmap;
/**
* 位图法
* 1.位图法是用一个bit的0/1代表这个bit所在位置的值存在与否
* 2.计算机的运算都是基于补码:
* 2.1.正数的原码、反码、补码均相同;负数的反码除了符号位其它位均取反,补码为反码(不考虑符号位)加一
* 2.2.补码转原码只需要对补码再求补码即可
* 2.3.java没有无符号数(没有符号位的数为无符号数)
* 2.4.java的按位与(&)、或(|)、异或(^)、非(~)都是基于补码运算的
* 2.5.基于上面原则,直接用有符号数实现位图法时,需要对负数做额外处理
* <p>
* 例:a = -91, b = 27
* 原码:11011011 00011011
* 反码:10100100 00011011
* 补码:10100101 00011011
* a & b = 00000001 --补码转原码--> 00000001 = 1
* a | b = 10111111 --补码转原码--> 11000001 = -65
* a ^ b = 10111110 --补码转原码--> 11000010 = -66
* ^a = 01011010 --补码转原码--> 01011010 = 90
*/
public class BitMap {
private static final long MAX = Integer.MAX_VALUE;
private static final long MIN = Integer.MIN_VALUE;
private static final long SCOPE = MAX - MIN;
private static final int SIZE = (int) (SCOPE / 8 + 1);
private static final byte[] DIGIT = {1, 2, 4, 8, 16, 32, 64, -128};
private static volatile byte[] nums = new byte[SIZE];
private static volatile int size = 0;
/**
* 添加num
* 当数据量比较小时,随机抽取一个num,大概率是不存在的,若此时先find(num),find(num)的时间大概率是被浪费了,所以直接往下走
*
* @param num 待添加的数
*/
public static void add(int num) {
long numLong = num;
int index = (int) ((numLong - MIN) / 8);
int offset = (int) ((numLong - MIN) % 8);
byte tmp = nums[index];
if (tmp == 0) {
// 如果该byte为0,直接设值即可将对应bit设置为1
nums[index] = DIGIT[offset];
size++;
return;
}
boolean isNavigative = false;
// 处理数组中的数据,将负数转成正数
if (tmp == -128) {
isNavigative = true;
tmp = 0;
} else if (tmp < 0) {
isNavigative = true;
tmp = (byte) (-tmp);
}
if (offset == 7) {
// 符号位的值变为1
if (isNavigative) {
// 负数的符号位已经为1,无需重复添加
return;
} else if (tmp == 0) {
// 如果值为0,需要将符号位设置为1(-128)。--由于前面有对0的判断,所以不会走到该分支
nums[index] = DIGIT[offset];
size++;
} else {
// 对于正数,取相反数即可将符号位设置为1
nums[index] = (byte) (-tmp);
size++;
}
} else {
// 对于不是存储在最高位的数,必然是正数,直接按位或,再根据数组中原始数据是否为负处理符号位
int ori = tmp;
tmp = (byte) (tmp | DIGIT[offset]);
if (ori != tmp) {
size++;
if (isNavigative) {
nums[index] = (byte) (-tmp);
} else {
nums[index] = tmp;
}
}
}
}
/**
* 移除num
* 当数据量比较小时,随机抽取一个num,大概率是不存在的,所以先find(num),存在时再处理是比较省时间的
*
* @param num 待移除的数
*/
public static void remove(int num) {
if (!find(num)) {
// 如果数不存在,跳过
return;
}
size--;
long numLong = num;
int index = (int) ((numLong - MIN) / 8);
int offset = (int) ((numLong - MIN) % 8);
byte tmp = nums[index];
if (offset == 7) {
// 移除符号位的1
if (tmp == -128) {
// 只有符号位为1,直接设值为0
nums[index] = 0;
} else if (tmp < 0) {
// 若为负数,取绝对值即可移除符号位的1
nums[index] = (byte) (-tmp);
} else {
// 正数的符号位为0。--由于前面有判断是否有值,所以不会走到该分支
return;
}
} else {
// 移除非符号位
if (tmp == -128) {
// 只有符号位为1。--由于前面有判断是否有值,所以不会走到该分支
return;
} else if (tmp < 0) {
// 小于0时,先转换成正数
tmp = (byte) (-tmp);
tmp -= DIGIT[offset];
nums[index] = (byte) (-tmp);
} else {
tmp -= DIGIT[offset];
nums[index] = tmp;
}
}
}
/**
* 查找num是否存在
*
* @param num 带查找的数
* @return true-存在/false-不存在
*/
public static boolean find(int num) {
long numLong = num;
int index = (int) ((numLong - MIN) / 8);
byte tmp = nums[index];
if (tmp == 0) {
// 无任何数
return false;
}
int offset = (int) ((numLong - MIN) % 8);
if (offset == 7) {
// 若待查找的数在符号位,直接判断符号位的值
return tmp < 0;
} else {
// 待查找的数不在符号位
if (tmp == -128) {
// -128只有符号位的值为1
return false;
} else if (tmp < 0) {
// 其它负数需要先转换为正数
tmp = (byte) (-tmp);
// 按位与,若对应位置的值为1,则结果大于0
return (tmp & DIGIT[offset]) > 0;
} else {
// 同上
return (tmp & DIGIT[offset]) > 0;
}
}
}
/**
* 返回bitmap中所有存在的数
*
* @return bitmap中所有存在的数
*/
public static int[] list() {
int[] allNum = new int[size];
int index = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0) {
byte num = nums[i];
int position = Integer.MIN_VALUE + 8 * i;
index = addByteCount(allNum, index, position, num);
}
}
return allNum;
}
/**
* 解析byte中存储的数字
*
* @param allNum 所有数字
* @param index allNum中下一个数字的下标
* @param position num对应的第一个数字
* @param num 表示8个数字的一个byte
* @return allNum中下一个数字的下标
*/
private static int addByteCount(int[] allNum, int index, int position, byte num) {
if (num == -128) {
// 只有一个数字
allNum[index] = position + 7;
return ++index;
}
// 处理负数
boolean isNavigative = false;
int numValue;
if (num < 0) {
isNavigative = true;
numValue = -num;
} else {
numValue = num;
}
// 处理符号位以外的bit
int i = 0;
while (numValue > 0) {
if (numValue % 2 == 1) {
allNum[index] = position + i;
index++;
}
numValue /= 2;
i++;
}
// 若符号位有数字,需要添加符号位的数字
if (isNavigative) {
allNum[index] = position + 7;
index++;
}
return index;
}
/**
* 返回bitmap中数字的个数
*
* @return 数字个数
*/
public static int size() {
return size;
}
public static void main(String[] args) {
System.out.println(find(1));
add(Integer.MIN_VALUE);
add(Integer.MIN_VALUE);
add(Integer.MIN_VALUE + 1);
add(Integer.MIN_VALUE + 2);
add(Integer.MIN_VALUE + 3);
add(Integer.MIN_VALUE + 4);
add(Integer.MIN_VALUE + 5);
add(Integer.MIN_VALUE + 6);
add(Integer.MIN_VALUE + 6);
add(Integer.MIN_VALUE + 7);
add(Integer.MIN_VALUE + 7);
add(Integer.MIN_VALUE + 8);
add(Integer.MIN_VALUE + 8);
add(Integer.MIN_VALUE + 9);
add(Integer.MIN_VALUE + 9);
add(Integer.MAX_VALUE);
add(Integer.MAX_VALUE);
add(Integer.MAX_VALUE - 1);
add(Integer.MAX_VALUE - 2);
add(Integer.MAX_VALUE - 3);
add(Integer.MAX_VALUE - 4);
add(Integer.MAX_VALUE - 5);
add(Integer.MAX_VALUE - 6);
add(Integer.MAX_VALUE - 7);
add(Integer.MAX_VALUE - 7);
add(Integer.MAX_VALUE - 8);
add(Integer.MAX_VALUE - 9);
add(1);
add(-1);
add(0);
add(3);
add(-2313);
add(334);
System.out.println(size());
System.out.println("-----list num start------");
for (int i : list())
System.out.println(i);
System.out.println("-----list num end------");
System.out.println(find(0));
remove(100);
remove(0);
remove(Integer.MAX_VALUE);
remove(Integer.MAX_VALUE);
System.out.println(size());
System.out.println(find(Integer.MAX_VALUE));
System.out.println(find(Integer.MIN_VALUE));
}
}