BitMap源码分析,使用场景
hutool包下的BitMap接口
public interface BitMap{
int MACHINE32 = 32; // 32/8=4适用于四个字节的类型,int
int MACHINE64 = 64; // 54/8=8适用于八个字节的类型,long
/**
* 加入值
*
* @param i 值
*/
void add(long i);
/**
* 检查是否包含值
*
* @param i 值
* @return 是否包含
*/
boolean contains(long i);
/**
* 移除值
*
* @param i 值
*/
void remove(long i);
}
IntMap源码分析
package cn.hutool.bloomfilter.bitMap;
import java.io.Serializable;
/**
* 过滤器BitMap在32位机器上.这个类能发生更好的效果.一般情况下建议使用此类
* 9ee50296-9e4d-4beb-8d78-5c52f80d7f78
* @author loolly
*
*/
public class IntMap implements BitMap, Serializable {
private static final long serialVersionUID = 1L;
private final int[] ints;
/**
* 构造
*/
public IntMap() {
ints = new int[93750000];
}
/**
* 构造
*
* @param size 容量
*/
public IntMap(int size) {
ints = new int[size];
}
@Override
public void add(long i) {
int r = (int) (i / BitMap.MACHINE32);
int c = (int) (i % BitMap.MACHINE32);
ints[r] = ints[r] | (1 << c);
}
@Override
public boolean contains(long i) {
int r = (int) (i / BitMap.MACHINE32);
int c = (int) (i % BitMap.MACHINE32);
return ((ints[r] >>> c) & 1) == 1;
}
@Override
public void remove(long i) {
int r = (int) (i / BitMap.MACHINE32);
int c = (int) (i % BitMap.MACHINE32);
ints[r] &= ~(1 << c);
}
}
初始化
private final int[] ints;
/**
* 构造
*/
public IntMap() {
ints = new int[93750000];
}
/**
* 构造
*
* @param size 容量
*/
public IntMap(int size) {
ints = new int[size];
}
默认初始化为ints = new int[93750000];
,可以存储93750000 * 32 = 30_0000_0000
共30亿个数据。也可以自定义初始化。
添加操作
@Override
public void add(long i) {
int r = (int) (i / BitMap.MACHINE32); //除以32,计算存储在int数组哪个索引位置
int c = (int) (i % BitMap.MACHINE32); //除以32取余,计算存储在该索引位置的int类型数字的第几个比特位上
ints[r] = ints[r] | (1 << c);
}
- 默认9375000个int整型,计算存储在哪个int上
- 一个整型32个比特位,计算存储在哪个bit位上
- 计算出要存储的数组位置索引,以及比特位,把该比特位置为1
ints[r] = ints[r] | (1 << c);
:先把1左移c位,计算出要改为1的bit位,然后与该int通过或运算,进行对应位置赋值。
为什么要用或运算:比如该int原本就存储了一些数字,0001_0011_…,int共32位,略去其他24位
现在添加了一个元素,要把第7个位置该为1:1000_0000_…
1000_0000 |
0001_0011 =
1001_0011 即存储了元素,也不影响之前存储的元素的值
判断数字是否存在
@Override
public boolean contains(long i) {
int r = (int) (i / BitMap.MACHINE32);
int c = (int) (i % BitMap.MACHINE32);
return ((ints[r] >>> c) & 1) == 1;
}
基本上与添加操作一样,先判断位置,然后判断该位置的bit位是否为1
该int原本存储了一些数字:1001_0011_…判断第4位是否为1:
1001_0011 >>> 4 == 0000_1001,无符号右移,用0填充
0000_1001 & 1 即:0000_1001 & 0000_0001 == 0000_0001 ==1
移除元素
@Override
public void remove(long i) {
int r = (int) (i / BitMap.MACHINE32);
int c = (int) (i % BitMap.MACHINE32);
ints[r] &= ~(1 << c);
}
移除操作稍微与其他两个有一点不同,示例说明:
该int存在一些元素 0000_0011,现在要移除第1位的数字,变为0000_0001
首先1左移1位,0000_0001 << 1 == 0000_0010
然后取反-------------------------------------1111_1101
与原int相与----------------------------------0000_0011
得到结果--------------------------------------0000_0001
LongMap
LongMap与IntMap同理,只不过long类型占8个字节,64个比特位。
使用场景
存储海量数据,如20个亿的电话号码。
一个电话号码11位,除去第一位为1固定。需要判断20亿个10位数字是否重复。
如果用String类型存储,JDK新版本优化String为byte数组,存储一个号码需要101=10个字节,
20亿号码需要1020亿=20_000_000_000,大概需要20G内存。
如果用long存储,一个号码需要8个字节,大概也需要一二个G。
使用BitMap存储海量号码
用BitMap存储,一个号码:00_0000_0000 --> 99_9999_9999,共100亿种可能,完全可以表示20亿个的电话号码
只需用100亿个bit位即可表示。第0个bit位为1,表示存在号码:00_0000_0000,
第99_9999_9999个bit位为1,表示存在号码:99_9999_9999。
100亿/8/10亿,约等于只需要1个多G,就可以表示所有号码。
//假设添加号码:10_0860_0000
//该数字为10位数,最大约等于100亿,int的最大值为2的32次方-1,超过int的表示范围,所以用long类型
// 10_0860_0000/64得到要存储的long数组的索引
// 10_0860_0000%64得到该索引64位中的具体哪一位
//进行存储......如上述源码