
Bitmap 是基于位的数据结构,每一位代表一个元素是否存在。
对于一个范围在 0 到 N - 1 的整数集合,如果使用普通的布尔数组来表示每个整数是否存在,需要占用 N 个字节的内存空间(假设布尔值占用 1 个字节)。
而使用 Bitmap,只需要 N / 8 个字节(因为 1 个字节有 8 位),大大节省了内存。
比如要存储 0 到 999999 的整数,普通布尔数组需要 1MB 内存,而 Bitmap 只需要 125KB,节省了 87.5% 的内存空间。
用字节数组来实现 Bitmap,每个字节包含 8 位,可以表示 8 个数字:
public class BitmapDeduplication {
// 定义 Bitmap 的大小,这里假设处理的数据范围在 0 到 999999
private static final int BITMAP_SIZE = 1000000;
// 字节数组用于存储 Bitmap 数据
private byte[] bitmap = new byte[BITMAP_SIZE / 8];contains方法public boolean contains(int value) {
// 计算元素对应的字节索引
int byteIndex = value / 8;
// 计算元素在字节中的位索引
int bitIndex = value % 8;
// 通过位运算检查该位是否被设置
return (bitmap[byteIndex] & (1 << bitIndex))!= 0;
}这个方法的逻辑很简单:先算出数字在哪个字节(除以8),再算出在字节的哪一位(取余8),最后用位运算检查这一位是不是1。
用生活例子来理解:
把Bitmap想象成一排开关,每个开关代表一个数字:
具体步骤(以数字19为例):
19 ÷ 8 = 2 余 31 << 3 制作一个"探测器" 00001000解释:开关组状态就是内存中实际存储的字节值,每一位代表一个数字是否存在。
简单理解:就像用手电筒照特定位置,如果那个位置有光(1),手电筒就能照到;如果没光(0),就照不到。
add方法public void add(int value) {
int byteIndex = value / 8;
int bitIndex = value % 8;
// 使用位运算设置相应的位为 1
bitmap[byteIndex] |= (1 << bitIndex);
}添加元素就是把对应的位设置为1。先找到位置,然后用按位或运算把那一位变成1,其他位保持不变。
用生活例子来理解:
添加数字就是"打开开关":
1 << 3 制作一个"开关器" 00001000解释:原来状态是操作前bitmap中的实际数据,通过位运算修改后变成新的状态。
简单理解:就像按电灯开关,不管原来是开是关,按了之后肯定是开的。其他开关不受影响。
为什么这样设计?
main方法public static void main(String[] args) {
BitmapDeduplication deduplicator = new BitmapDeduplication();
int[] data = {1, 2, 3, 4, 2, 5, 1, 6};
for (int num : data) {
if (!deduplicator.contains(num)) {
deduplicator.add(num);
System.out.println("Unique element: " + num);
}
}
}运行这段代码,输出结果是:1, 2, 3, 4, 5, 6。重复的数字被自动过滤掉了。
sun.misc.Unsafe 不推荐使用,但在性能要求极高的场景下可以考虑。 public void addRange(int start, int end) {
int startByteIndex = start / 8;
int endByteIndex = end / 8;
int startBitIndex = start % 8;
int endBitIndex = end % 8;
if (startByteIndex == endByteIndex) {
// 在同一个字节内
byte mask = (byte) ((1 << (endBitIndex - startBitIndex + 1)) - 1 << startBitIndex);
bitmap[startByteIndex] |= mask;
} else {
// 跨越多个字节
byte startMask = (byte) ((1 << (8 - startBitIndex)) - 1 << startBitIndex);
bitmap[startByteIndex] |= startMask;
for (int i = startByteIndex + 1; i < endByteIndex; i++) {
bitmap[i] = (byte) 0xff;
}
byte endMask = (byte) ((1 << (endBitIndex + 1)) - 1);
bitmap[endByteIndex] |= endMask;
}
}批量操作的性能提升非常明显,特别是处理连续数据时。
1 << bitIndex 很浪费。预先算好 8 个掩码,直接查表: private static final byte[] BIT_MASKS = new byte[8];
static {
for (int i = 0; i < 8; i++) {
BIT_MASKS[i] = (byte) (1 << i);
}
}
public boolean contains(int value) {
int byteIndex = value / 8;
int bitIndex = value % 8;
return (bitmap[byteIndex] & BIT_MASKS[bitIndex])!= 0;
}这个小优化在高频调用时能提升 10-20% 的性能。
public synchronized boolean contains(int value) {
//...
}
public synchronized void add(int value) {
//...
} 更高级的做法是用 AtomicIntegerArray,性能比 synchronized 好一些。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。