RoaringBitmap 是一种高效的位图数据结构,它通过压缩技术有效地存储和操作大量整数数据,特别适合整数集合的集合运算(如并集、交集和差集)。其中,RoaringArray 是 RoaringBitmap 的核心部分之一,它管理多个容器,用于存储整数的分片数据。RoaringArray 使用了分片机制,将整数集合划分为多个“块”(block),并为每个块配备一个 key 和一个 value。
在 RoaringBitmap 中,RoaringArray 的 key 和 value 的底层原理如下:
1. RoaringBitmap 的基本结构和分片机制
RoaringBitmap 的设计基于分片(partitioning)的思想。整个整数范围被分成固定大小的块,每个块包含 2^16(即 65536)个连续的整数。一个整数 x 被分解为两个部分:
- 高 16 位作为块的
key。 - 低 16 位作为块内的偏移量,用于找到具体的
value。
例如,假设我们有一个整数 x,它的二进制表示为 xxxxxxxxxxxxxxxx_yyyyyyyyyyyyyyyy,其中:
xxxxxxxxxxxxxxxx是高 16 位,作为key。yyyyyyyyyyyyyyyy是低 16 位,作为块内偏移量,即value。
RoaringBitmap 通过这种分片机制将大范围的整数集合切分成多个小块,分别存储在不同的容器(container)中,从而实现了高效的存储和查询。
2. RoaringArray 的 key 和 value
在 RoaringArray 中,key 是一个 short 类型(16 位的整数),用于标识每个分片或块。value 则是一个指向具体容器的引用(Container),用于存储属于该块的所有整数。
每一个 Container 都存储 0 到 65535 之间的整数(即 2^16 的范围),并使用不同的内部结构(如 ArrayContainer、BitmapContainer 或 RunContainer)来存储数据。Container 的类型取决于数据的密度,以选择合适的存储方式。
具体来说:
key: 表示块的高 16 位(用于区分不同的块)。value: 一个Container实例,包含块内的所有数据。
3. RoaringArray 的内部实现
RoaringArray 本质上是一个包含两个数组的类:
keys: 一个short数组,存储每个块的key。values: 一个Container数组,存储与key对应的容器。
RoaringArray 维持了 keys 数组的排序,以便在执行操作时可以通过二分查找快速定位目标块。例如,在查找或添加一个新的整数时,RoaringArray 可以通过二分查找快速定位相应的 key,并使用对应的 Container 存取数据。
public class RoaringArray implements Cloneable {
// `keys`数组:存储块的key
protected short[] keys;
// `values`数组:存储对应的容器(ArrayContainer, BitmapContainer, RunContainer等)
protected Container[] values;
// 当前数组中存储的元素数量
protected int size;
// 构造函数
public RoaringArray() {
this.keys = new short[INITIAL_CAPACITY];
this.values = new Container[INITIAL_CAPACITY];
this.size = 0;
}
// 添加新的(key, value)对到RoaringArray
public void append(short key, Container value) {
ensureCapacity(size + 1);
keys[size] = key;
values[size] = value;
size++;
}
// 二分查找,找到对应key的位置
public int binarySearch(short key) {
int low = 0;
int high = size - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
short midVal = keys[mid];
if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found
}
// 通过key找到对应的Container
public Container getContainer(short key) {
int index = binarySearch(key);
return index >= 0 ? values[index] : null;
}
}
4. Container 的具体实现
Container 是 RoaringBitmap 中的一个接口,用于存储每个块中的具体数据。根据块中整数的密度和分布情况,RoaringBitmap 采用不同的 Container 实现来优化存储和访问效率:
- ArrayContainer:当一个块中的整数数量较少时,使用
ArrayContainer。它将整数存储在一个short数组中,适合稀疏的情况。 - BitmapContainer:当一个块中包含较多的整数时,使用
BitmapContainer,它使用位图来存储整数,适合密集的情况。 - RunContainer:当块中的整数序列包含大量连续的数据时,使用
RunContainer,它将整数范围压缩存储,适合长序列的情况。
这些 Container 类实现了相同的接口,因此可以根据数据分布动态选择最合适的容器,从而减少内存占用并提高访问效率。
例如,ArrayContainer 的实现:
public class ArrayContainer extends Container {
private short[] array; // 存储实际的整数
private int cardinality; // 记录当前元素数量
public ArrayContainer() {
this.array = new short[INITIAL_CAPACITY];
this.cardinality = 0;
}
// 添加元素到ArrayContainer
public void add(short value) {
if (cardinality == array.length) {
increaseCapacity();
}
array[cardinality++] = value;
}
}
5. 操作流程示例
我们将从一个实际的例子入手,详细说明 ArrayContainer 是如何在 RoaringBitmap 中存储和读取数据的。
假设我们有一个 RoaringBitmap,其中存储了一些整数集合 {1, 2, 3, 65536, 65537, 65538}。这些整数经过 RoaringBitmap 的分片机制会被分为不同的块,每个块包含 65536 个连续的整数。ArrayContainer 将负责其中某些块的数据存储和管理。
这些整数将被划分为两个不同的块:
-
第一个块: 包含
{1, 2, 3}。- 对于整数
1, 2, 3,它们的高 16 位是0,低 16 位分别是1, 2, 3。 - 因此,
key = 0,表示该整数属于第一个块。
- 对于整数
-
第二个块: 包含
{65536, 65537, 65538}。- 对于整数
65536, 65537, 65538,它们的高 16 位是1,低 16 位分别是0, 1, 2。 - 因此,
key = 1,表示该整数属于第二个块。
- 对于整数
使用 ArrayContainer 存储数据
RoaringBitmap 使用 RoaringArray 来存储不同块的 key 和 value,其中 value 是指向具体容器(Container)的引用。在这个例子中,我们会为 key = 0 和 key = 1 分别分配一个 ArrayContainer 来存储对应的整数。
1. 存储 {1, 2, 3} 到 ArrayContainer
假设 RoaringArray 中的 key = 0 对应一个 ArrayContainer,该容器会存储块内偏移量 [1, 2, 3]。以下是具体的存储步骤:
- 初始化一个
ArrayContainer。 - 将低 16 位的值依次添加到
ArrayContainer中,即1,2,3。 ArrayContainer的内部数组会存储[1, 2, 3]。
ArrayContainer arrayContainer1 = new ArrayContainer();
arrayContainer1.add((short) 1);
arrayContainer1.add((short) 2);
arrayContainer1.add((short) 3);
2. 存储 {65536, 65537, 65538} 到 ArrayContainer
同理,对于 key = 1,RoaringArray 会分配另一个 ArrayContainer,并存储块内偏移量 [0, 1, 2]:
ArrayContainer arrayContainer2 = new ArrayContainer();
arrayContainer2.add((short) 0);
arrayContainer2.add((short) 1);
arrayContainer2.add((short) 2);
ArrayContainer 的内部存储结构
每个 ArrayContainer 内部存储的数据结构如下:
arrayContainer1中保存[1, 2, 3]。arrayContainer2中保存[0, 1, 2]。
在 RoaringArray 中映射 key 到 ArrayContainer
RoaringArray 将 key = 0 和 key = 1 分别映射到 arrayContainer1 和 arrayContainer2:
RoaringArray roaringArray = new RoaringArray();
roaringArray.append((short) 0, arrayContainer1);
roaringArray.append((short) 1, arrayContainer2);
读取数据
假设我们想检查整数 65537 是否存在于 RoaringBitmap 中。以下是查找过程的步骤:
-
分离高低 16 位:
- 取出整数
65537的高 16 位:1,这就是key。 - 低 16 位是
1,这是块内偏移量。
- 取出整数
-
查找对应的 Container:
- 在
RoaringArray中找到key = 1的位置。 - 获取对应的
ArrayContainer,即arrayContainer2。
- 在
-
在 ArrayContainer 中查找偏移量:
- 在
arrayContainer2中查找1。 arrayContainer2包含[0, 1, 2],因此找到了1,说明整数65537存在于 RoaringBitmap 中。
- 在
代码示例:存储和读取
完整的存储和读取过程代码如下:
public class RoaringBitmapExample {
public static void main(String[] args) {
// 创建 RoaringArray 并初始化 ArrayContainer
RoaringArray roaringArray = new RoaringArray();
// 初始化第一个块(key = 0)的 ArrayContainer
ArrayContainer arrayContainer1 = new ArrayContainer();
arrayContainer1.add((short) 1);
arrayContainer1.add((short) 2);
arrayContainer1.add((short) 3);
roaringArray.append((short) 0, arrayContainer1);
// 初始化第二个块(key = 1)的 ArrayContainer
ArrayContainer arrayContainer2 = new ArrayContainer();
arrayContainer2.add((short) 0);
arrayContainer2.add((short) 1);
arrayContainer2.add((short) 2);
roaringArray.append((short) 1, arrayContainer2);
// 检查整数 65537 是否存在
int target = 65537;
short key = (short) (target >>> 16); // 高 16 位
short offset = (short) (target & 0xFFFF); // 低 16 位
// 在 RoaringArray 中查找 key 对应的 ArrayContainer
Container container = roaringArray.getContainer(key);
if (container != null && container.contains(offset)) {
System.out.println("整数 " + target + " 存在于 RoaringBitmap 中");
} else {
System.out.println("整数 " + target + " 不存在于 RoaringBitmap 中");
}
}
}
输出结果
数 65537 存在于 RoaringBitmap 中
总结
RoaringArray的key: 用于标识 RoaringBitmap 中的不同块,表示整数的高 16 位。RoaringArray的value: 指向对应的Container,存储块内所有的整数,表示整数的低 16 位。Container实现: 不同的Container实现(ArrayContainer、BitmapContainer、RunContainer)使得 RoaringBitmap 可以根据块内数据密度选择最合适的存储结构,从而实现高效存储和快速操作。
通过这种分片存储结构和不同类型的容器选择,RoaringBitmap 实现了对大量整数数据的高效存储和快速集合操作。
569

被折叠的 条评论
为什么被折叠?



