Java手写BitMap
有这么个需求
如何在3亿个整数(0~2亿)中判断某一个数是否存在?内存限制500M,一台机器。
在这里就用BitMap实现
说代码实现前,先来点基础知识
运算符
-
异或运算符(^)
运算规则:0^0=0; 0^1=1; 1^0=1; 1^1=0;即:参加运算的两个对象,如果两个相应位为“异”(值不同),则该位结果为1,否则为0。
-
位与(&): 同位上的两个数都是1则位1,否则为0
位或 (|): 同为上的两个数只要有一个为1 则为1,否则为0 -
左移
左移 << : 8 << 2 : 8*4=32 ,整体左边移动2位,补俩个0,乘以2的平方 -
右移
右移 >>: 8 >>2 : 8 / 4 = 2,整体右边移2位,左边补2个0,除以2的平方
基础知识讲完开始
分析下BitMap
我们知道 一个int占32个bit位。
假如我们用这个32个bit位的每一位的值来表示一个数的话是不是就可以表示32个数字,也就是说32个数字只需要一个int所占的空间大小就可以了,瞬间就可以缩小空间32倍。
比如假设我们有N{1,5,64}个数中最大的是MAX,那么我们只需要开int[MAX /32+1]个int数组就可以存储完这些数据。
Int a : 0000 0000 0000 0000 0000 0000 0000 0000 这里是32个位置,我们可以利用每个位置的0或者1来表示该位置的数是否存在,这样我们就可以得到以下存储结构:具体我们可以画个图来展示
那我们的1,5,64就是存到了这里边,
假设我们要判断64是否在列表中,那么我们就可以这样来计算:
64/32=2 =>定位到data[2],64%32=0 定位到data[2]的第1位(注意这里从右边开始数)。我们再来看data[2]的第一位是否是1,如果是1则列表中存在64,否则不存在。
这就是bitMap的核心思想。
如果不用int,那么如果换成byte数组来存呢?那么每行就是8位,思想是一样的。
代码实现下,先看byte的实现
byte数组实现BitMap
public class BitMap {
byte[] bits; //如果是byte那就一个只能存8个数,一个字节是8位
int max; //表示最大的那个数
public BitMap(int max) {
this.max = max;
bits = new byte[(max >> 3) + 1]; //max/8 + 1
}
public void add(int n) { //往bitmap里面添加数字
int bitsIndex = n >> 3; // 除以8 就可以知道在那个byte
int loc = n & 7; ///这里其实还可以用&运算
//接下来就是要把bit数组里面的 bisIndex这个下标的byte里面的 第loc 个bit位置为1
bits[bitsIndex] |= 1 << loc; //或运算
}
public void delete(int n){
int bitsIndex = n >> 3;
int loc = n & 7;
bits[bitsIndex] ^= 1 << (loc);//异或运算
}
public boolean find(int n) {
int bitsIndex = n >> 3; // 除以8 就可以知道在那个byte
int loc = n & 7; ///这里其实还可以用求余运算,等同于%8
int flag = bits[bitsIndex] & (1 << loc); //如果原来的那个位置是0 那肯定就是0 只有那个位置是1 才行
if(flag == 0) return false;
return true;
}
public static void main(String[] args) {
BitMap bitMap = new BitMap(200000001); //10亿
bitMap.add(2);
bitMap.add(3);
bitMap.add(65);
bitMap.add(66);
bitMap.delete(66);
System.out.println(bitMap.find(65));
System.out.println(bitMap.find(66));
}
}
这里边利用位与运算代替求余运算了。
int数组实现BitMap
public class BitMapByInt {
int[] data;
int max; //表示最大的那个数
public BitMapByInt(int max) {
this.max = max;
data = new int[(max >> 5) + 1]; //max/32 + 1
}
public void add(int n) { //往bitmap里面添加数字
int index = n >> 5; // 除以32 就可以知道在那个int
int loc = n & 31; ///这里其实还可以用&运算
//接下来就是要把数组里面的 index这个下标的int里面的 第loc 个bit位置为1
data[index] |= 1 << loc; //或运算
}
public void delete(int n){
int index = n >> 5;
int loc = n & 31;
data[index] ^= 1 << (loc);//异或运算
}
public boolean find(int n) {
int index = n >> 5; // 除以8 就可以知道在那个byte
int loc = n & 31; ///这里其实还可以用&运算,等同于%32
int flag = data[index] & (1 << loc); //如果原来的那个位置是0 那肯定就是0 只有那个位置是1 才行
if(flag == 0) return false;
return true;
}
public static void main(String[] args) {
BitMapByInt map = new BitMapByInt(200000001); //10亿
map.add(2);
map.add(3);
map.add(65);
map.add(66);
map.delete(2);
System.out.println(map.find(2));
System.out.println(map.find(3));
}
}
BitMap的缺点
利用BitMap可以找不重复的数,统计数据,那么也有缺点
- 数据不能重复:数据只有0和1 也就是有或者没有 不知道有多个
- 数据量少时相对于普通的hash没有优势
- 无法处理字符串:hash冲突