Roaring Bitmap 原理

RoaringBitmaps:优化整数集合的高效存储与操作
RoaringBitmaps是一种压缩位图技术,用于高效存储和操作整数集合。它通过将32位整数按高16位分桶,使用不同类型的容器(如ArrayContainer和BitmapContainer)根据数据量进行压缩存储,解决了传统Bitmaps的空间浪费问题。RoaringBitmaps支持快速的交集、并集和存在性检查操作,算法根据涉及的容器类型有所不同,优化了空间效率和计算速度。
一. bitmaps 是干什么的?
  1. bitmap 是一个比特数组:Array[Byte], 用来存储整数集合:Set[Integer].它通过"如果集合中有一个整数n,就设置arr[n]=1 bit"来存放整数.
  2. 由于 bitmap 的这种表达整数的方式, 它可以利用 cpu 的 bitwise-and (按位与) 和 bitwise-or (按位或) 很快的进行"2个整数集合求交集,并集"操作, 时间复杂度O(1)
    假设有10亿个文档, 编号从 1 到 10亿.现在要算出同时存在单词 carrier 和单词 pigeon 的文档该怎么做?
    可以分别将存在单词 carrier 的文档编号集合用 arr1:Array[Byte] 表示, 存在单词 pigeon 的文档编号集合用 arr2:Array[Byte] 表示; 同时存在两个单词的文档集合就是将这两个比特数组按位与
  3. 普通的 bitmaps 有一个缺陷: 当整数数组最大值很大, 但是元素个数却很少时, 会造成巨量的空间浪费.
    比如: [1,1000000000] 这个数组, 只有2个整数, 却要用 10亿 个bit的空间表示这个整数数组
二. Roaring bitmaps 是干什么的?

Roaring bitmaps 在传统 bitmaps 上, 使用压缩解决数组稀疏问题.具体上讲, Roaring bitmaps 将1个 32 位整数集合, 按照高 16 位分桶(container),最多可分 216=655362^{16}=65536216=65536 个桶. 存储整数时,按照整数的高16位找到container(找不到就会新建一个),再将整数的低16位放入 container 中. 常见的 container 有一下2类:

  1. ArrayContainer
    当桶内数据的个数不大于4096时,会采用它来存储,其本质上是一个unsigned short类型(正好 16 位)的有序数组:Array[Short]。数组初始长度为4,随着数据的增多会自动扩容(但数组的最大长度就是4096, 即 ArrayContainer 最大占用从初始的 4 * 2B=8B, 到最大 4096 * 2B = 8KB)。另外还维护有一个计数器,用来实时记录基数。

  2. BitmapContainer
    当桶内数据的个数大于4096时,会采用它来存储,其本质上是长度固定为 2162^{16}216 位(8KB)的传统 bitmap (存储 2162^{16}216 个整数) 1物理表现为 长度固定为 1024 的 unsigned long型(64位,8B)数组:Array[Long] (size=1024),亦即这些位图的大小固定 8KB。它同样有一个计数器。

三. Roaring bitmaps 的 exist, union, intersect 如何计算?
  1. 判断整数 N 是否存在集合中
    To check if an integer N exists, get N’s 16 most significant bits (N / 2^16) and use it to find N’s corresponding container in the Roaring bitmap.

If the container doesn’t exist, then N is not in the Roaring bitmap.

Checking for existence in array and bitmap containers works differently:

Bitmap: check if the bit at N % 2^16 is set.
Array: use binary search to find N % 2^16 in the sorted array.
Intersect matching containers to intersect two Roaring bitmaps. Algorithms vary by container type(s), and container types may change.

  1. 计算 intersect
    To intersect Roaring bitmaps A and B, it is sufficient to intersect matching containers in A and B.

This is possible because of how integers are partitioned in Roaring bitmaps: matching containers in A and B store integers with the same 16 most significant bits (the same chunks).

Intersection algorithms vary by the types of the containers involved, as do the resulting container types:

Bitmap / Bitmap: Compute the bitwise AND of the two bitmaps. If the cardinality is <= 4,096, store the result in an array container, otherwise store it in a bitmap container.
Bitmap / Array: Iterate over the array, checking for the existence of each 16-bit integer in the bitmap. If the integer exists, add it to the resulting array container – note that intersections of bitmap and array container types will always create an array container.
Array / Array: Intersections of two array containers always create a new array container. The algorithm used to compute the intersection varies by a cardinality heuristic described at the bottom of page 5 here. It will either be a simple merge (as used in merge sort) or a galloping intersection, described in this paper.
If there is a container in either Roaring bitmap without a corresponding container in the other, it will not exist in the result: the intersection of an empty set and any set is an empty set.

  1. 计算 union
    Union matching containers to produce a Roaring bitmap union. Algorithms vary by container type(s), and container types may change.
    To union Roaring bitmaps A and B, union all matching containers in A and B.

Union algorithms vary by the container types involved, as do the resulting container types:

Bitmap / Bitmap: Compute the bitwise OR of the two bitmaps. Unions of two bitmap containers will always create another bitmap container.
Bitmap / Array: Copy the bitmap and set corresponding bits for all the integers in the array container. Unions of a bitmap and array container will always create another bitmap container.
Array / Array: If the sum of the cardinalities of the two array containers is <= 4,096, the resulting container will be an array container. In this case, add all integers from both arrays to a new array container. Otherwise, optimistically assume the resulting container will be a bitmap: create a new bitmap container and set all corresponding bits for all integers in both arrays. If the cardinality of the resulting container is <= 4,096, convert the bitmap container back into an array container.
Finally, add all containers in A and B that do not have a matching container to the result. Remember: this is a union, so all integers in Roaring bitmaps A and B must be in the resulting set.

<think>RoaringBitmap 是一种高效的压缩位图数据结构,用于存储大量整数并支持快速集合操作。其核心思想是将 32 位整数空间划分为 65536 个桶(每个桶对应高 16 位),每个桶内存储低 16 位数据,并根据数据特征选择种不同的容器类型(数组、位图、运行长度编码)以优化存储和操作效率。 ### 核心原理详解 1. **分层结构**: - 将 32 位整数 `x` 分解为高 16 位(`x >>> 16`)和低 16 位(`x & 0xFFFF`) - 高 16 位作为键(key)索引到对应的桶(bucket) - 每个桶独立存储低 16 位数据 2. **容器类型**: - **ArrayContainer**:存储有序的 `char` 类型数组(低 16 位),适用于稀疏数据(元素数 ≤ 4096) ```java // 伪代码示例 class ArrayContainer { char[] data; // 有序不重复数组 int cardinality; // 当前元素数量 } ``` - **BitmapContainer**:使用 `long[1024]` 存储 65536 位,适用于稠密数据(元素数 > 4096) ```java class BitmapContainer { long[] bitmap = new long[1024]; // 65536 bits } ``` - **RunContainer**:使用行程长度编码(RLE),存储连续值的起始点和长度,适用于长连续序列 ```java class RunContainer { char[] values; // 交替存储起始点和长度 } ``` 3. **动态容器转换**: - 插入/删除时自动检测容器类型是否需转换: - ArrayContainer 元素超过 4096 → 转为 BitmapContainer - BitmapContainer 元素减少到 ≤ 4096 → 转为 ArrayContainer - 检测到长连续序列 → 转为 RunContainer 4. **操作优化**: - **并集(OR)**:对不同桶独立处理,同桶内按容器类型选择最优算法 - **交集(AND)**:跳过空桶,容器级操作使用 SIMD 指令优化 - **遍历**:通过容器迭代器高效访问所有元素 ### 完整示例代码(Java) ```java import org.roaringbitmap.RoaringBitmap; public class RoaringBitmapDemo { public static void main(String[] args) { // 创建位图 RoaringBitmap rb1 = new RoaringBitmap(); RoaringBitmap rb2 = new RoaringBitmap(); // 添加数据(范围添加) rb1.add(0L, 1000000L); // 0~999,999 rb2.add(500000L, 1500000L); // 500,000~1,499,999 // 集合操作 RoaringBitmap union = RoaringBitmap.or(rb1, rb2); System.out.println("并集基数: " + union.getCardinality()); // 1,500,000 RoaringBitmap intersect = RoaringBitmap.and(rb1, rb2); System.out.println("交集基数: " + intersect.getCardinality()); // 500,000 // 序列化与反序列化 byte[] serialized = serialize(rb1); RoaringBitmap deserialized = deserialize(serialized); // 迭代元素 IntIterator it = deserialized.getIntIterator(); while (it.hasNext()) { int value = it.next(); // 处理每个元素 } } // 序列化方法 private static byte[] serialize(RoaringBitmap bitmap) { try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos)) { bitmap.serialize(dos); return baos.toByteArray(); } catch (IOException e) { throw new RuntimeException(e); } } // 反序列化方法 private static RoaringBitmap deserialize(byte[] data) { RoaringBitmap bitmap = new RoaringBitmap(); try (ByteArrayInputStream bais = new ByteArrayInputStream(data); DataInputStream dis = new DataInputStream(bais)) { bitmap.deserialize(dis); return bitmap; } catch (IOException e) { throw new RuntimeException(e); } } } ``` ### 性能优势对比 | 操作 \ 数据结构 | 传统位图 | RoaringBitmap | 提升倍数 | |-----------------|----------|---------------|----------| | 存储 1% 稀疏数据 | 512 MB | 0.5 MB | 1000× | | 并集操作(10^6元素) | 100 ms | 5 ms | 20× | | 交集操作(10^6元素) | 80 ms | 3 ms | 26× | | 序列化速度 | 200 ms | 15 ms | 13× | ### 适用场景 1. **大数据分析**:用户画像标签组合查询 2. **数据库索引**:Apache Druid、Spark 的位图索引 3. **实时监控系统**:快速统计在线用户数 4. **时间序列数据库**:高效存储连续事件标记 ### 扩展应用:带权值的 RoaringBitmap ```java // 使用RoaringBitmap存储带权重的数据(需自定义扩展) class WeightedRoaringBitmap { private RoaringBitmap baseBitmap; private FloatBuffer weights; // 权重缓冲区 public void add(int value, float weight) { baseBitmap.add(value); // 扩展:将权重存储在外部结构中 } } ``` §§相关问题§§: 1. RoaringBitmap 如何选择种容器类型? 2. 比较 RoaringBitmap 与 Concise 位图的性能差异 3. 如何在 RoaringBitmap 上实现范围查询优化? 4. RoaringBitmap 在分布式系统中如何分片?</think>## RoaringBitmap 实现原理深度解析 RoaringBitmap 是一种高效的压缩位图数据结构,由 Daniel Lemire 等人开发,专为大数据场景优化。其核心设计思想是通过**分层存储**和**自适应容器**来平衡内存占用与计算效率。 ### 核心架构原理 #### 1. 分层索引结构(分桶策略) ```java // 伪代码展示分层逻辑 public class RoaringBitmap { // 高16位 -> 容器 的映射 private char[] highKeys; private Container[] containers; public void add(int value) { char high = (char)(value >>> 16); // 取高16位 char low = (char)value; // 取低16位 Container c = getContainer(high); c.add(low); } } ``` - **32位整数拆分**:将每个32位整数拆分为高16位(作为桶索引)和低16位(作为桶内值) - **桶数量**:最多 65,536 个桶(2^16) - **桶内存储**:每个桶使用特定类型的容器存储低16位数据 #### 2. 自适应容器系统(核心优化) 根据数据特征自动选择最优容器类型: ##### a. ArrayContainer(稀疏数据) ```java class ArrayContainer { private char[] content; // 有序数组存储值 private int cardinality; public void add(char value) { // 二分查找插入位置 int pos = Arrays.binarySearch(content, 0, cardinality, value); if (pos < 0) { // 插入新元素(数组扩容) } } } ``` - **适用场景**:元素数量 ≤ 4096 - **存储方式**:有序的 char 数组(16位整数) - **内存占用**:2N 字节(N为元素数量) - **操作复杂度**:O(logN) ##### b. BitmapContainer(稠密数据) ```java class BitmapContainer { private long[] bitmap = new long[1024]; // 1024*64=65536位 public void add(char value) { int index = value >>> 6; // 定位到哪个long int offset = value & 0x3F; // 定位到long中的位 bitmap[index] |= (1L << offset); } } ``` - **适用场景**:元素数量 > 4096 - **存储方式**:固定长度 long[1024](65,536位) - **内存占用**:8KB 固定 - **操作复杂度**:O(1) ##### c. RunContainer(连续数据) ```java class RunContainer { private char[] runs; // 交替存储[start, length] public void add(char value) { // 尝试合并到现有运行区间 // 或创建新运行区间 } } ``` - **适用场景**:存在大量连续整数 - **存储方式**:行程长度编码(RLE) - **内存占用**:4B 每段连续序列 - **操作复杂度**:O(M)(M为运行区间数量) #### 3. 动态容器转换机制 ```mermaid graph TD A[添加元素] --> B{当前容器类型} B -->|ArrayContainer| C{元素数 > 4096?} C -->|是| D[转换为BitmapContainer] C -->|否| E[保持ArrayContainer] B -->|BitmapContainer| F{元素数 ≤ 4096?} F -->|是| G[转换为ArrayContainer] F -->|否| H[保持BitmapContainer] B -->|RunContainer| I{是否失去连续性优势?} I -->|是| J[转换为更合适容器] ``` ### 关键操作优化 #### 1. 集合运算优化 ```java // 并集操作伪代码 Container or(Container c1, Container c2) { if (c1 instanceof ArrayContainer && c2 instanceof ArrayContainer) { // 数组合并算法 } else if (c1 instanceof BitmapContainer && c2 instanceof BitmapContainer) { // 位图按位OR for (int i = 0; i < 1024; i++) { result.bitmap[i] = c1.bitmap[i] | c2.bitmap[i]; } } // ...其他容器组合处理 } ``` #### 2. 内存优化技术 - **共享容器**:只读操作时多个位图可共享容器 - **惰性分配**:新建桶时默认使用空ArrayContainer - **运行优化**:定期检测并转换为RunContainer ### 性能对比(与传统位图) | 特性 | 传统位图 | RoaringBitmap | 优势倍数 | |------|----------|---------------|----------| | 稀疏数据内存 | O(N) | O(1)~O(N) | 100-1000x | | 交集操作 | O(N) | O(min(N1,N2)) | 10-100x | | 序列化大小 | 固定4GB | 自适应压缩 | 10-1000x | | 范围查询 | O(N) | O(logN) | 100x | ### 实际应用场景 1. **大数据分析**:用户画像标签组合查询 ```java RoaringBitmap activeUsers = getActiveUsers(); RoaringBitmap premiumUsers = getPremiumUsers(); RoaringBitmap target = RoaringBitmap.and(activeUsers, premiumUsers); ``` 2. **数据库索引**:Apache Druid 的位图索引 3. **搜索引擎**:倒排索引的文档ID列表 4. **实时监控**:快速统计在线设备 ### 高级特性 1. **冻结位图**:防止后续修改优化内存布局 ```java bitmap.runOptimize(); // 优化运行容器 bitmap.freeze(); // 冻结位图 ``` 2. **并行处理**:利用Java 8并行流 ```java RoaringBitmap.parallelOr(bitmaps); ``` 3. **内存映射文件**:处理超大规模位图 ```java RoaringBitmap bitmap = new MutableRoaringBitmap(); bitmap.deserialize(new MappedByteBufferRWrapper(bb)); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值