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);
	}
  1. 默认9375000个int整型,计算存储在哪个int上
  2. 一个整型32个比特位,计算存储在哪个bit位上
  3. 计算出要存储的数组位置索引,以及比特位,把该比特位置为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亿号码需要10
20亿=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位中的具体哪一位
//进行存储......如上述源码

posted @ 2024-03-23 17:18  ︶ㄣ演戲ㄣ  阅读(15)  评论(0编辑  收藏  举报  来源