活跃位图
什么是 Bitmap?
此处的 Bitmap 是一种索引类型。如果您是抱着图形学方面的想法点进来的,那么可以点击返回键了。
基本概念
Bitmap 是一种数据库索引技术。它将特定列的值表示为比特数组或向量的元素或形式,其中每个比特表示该列的特定值是否存在。因此,最基本的形式是为每一行分配一个比特索引,并用 0 和 1 表示其存在与否。
例如,假设有一个包含性别列的表。该表有 5 行,性别列的值如下:
- 男性
- 女性
- 男性
- 女性
- 其他
在这种情况下,性别列的 Bitmap 索引可以表示如下:
- 男性: 10100
- 女性: 01010
- 其他: 00001
那么它到底用在哪里呢?
因此,大家都会认为它不仅仅是为了表示这些。为了正确使用 Bitmap 索引,我们考虑以下情况。
1CREATE TABLE IF NOT EXISTS Users (
2 id INT PRIMARY KEY,
3 name VARCHAR(100),
4 active BOOLEAN,
5 sex BOOLEAN,
6 age INT
7);
假设此表中有 1,000,000 行。并且执行以下查询。
1SELECT * FROM Users WHERE active = TRUE AND SEX = TRUE;
此查询查找 active 和 SEX 列都为 TRUE 的行。那么,最快找到两个列都为 TRUE 的行的方法就是利用比特 AND 运算。
- 假设 active 列的 Bitmap 索引已预先索引为
1100110011...
(1,000,000 个比特数组), - 假设 sex 列的 Bitmap 索引已预先索引为
1010101010...
(1,000,000 个比特数组)。
那么,对这两个 Bitmap 索引执行比特 AND 运算,可以快速找到两个列都为 TRUE 的行。由于是简单的比特运算,因此获取结果的速度将比其他索引扫描快得多。
权衡
Bitmap 索引可能非常高效,但也有一些权衡。尽管存在许多其他缺点,但目前我们只提及一个。
那就是 Bitmap 索引更适用于低基数(即列中可能值的数量较少的情况)。在高基数列上使用 Bitmap 索引可能会导致 Bitmap 变得非常大。
在高基数列上使用 Bitmap 索引时,由于必须为每个唯一值生成单独的 Bitmap,因此可能需要大量的存储空间。例如,如果列中有 1,000,000 个唯一值,则必须生成 1,000,000 个 Bitmap,这可能非常低效。
那么我们
我们可以使用 Roaring Bitmap 来替代高基数带来的 Bitmap。Roaring Bitmap 具有多种特性,但其最大的基本理念和特性是“根据数据的密度动态选择最合适的存储方式,从而最大化压缩率和运算速度”。
Roaring Bitmap 的基本概念
两阶段索引
Roaring Bitmap 在存储一个整数时,首先需要经过以下两个阶段。说是两阶段索引,其实可以理解为二维数组。
- 提取高 16 位:提取整数的高 16 位,并将其用作索引。它成为一个长度为 65536 的二维数组中外部数组的索引。该数组存储了后续将提及的容器的地址。
- 提取低 16 位:提取整数的低 16 位,并将其用作容器内的位置。
容器
这个容器正如刚才比喻的那样,对应于二维数组中的内部数组。但是,与高 16 位的容器不同,这个容器的内部结构会根据内部数据的多少而变化。Roaring Bitmap 支持以下三种容器。
- Array Container:当内部存储的数据较少时使用此容器。具体来说,当存储少于 4096 个元素时使用。此容器仅由排序的整数数组实现。可以通过二分查找来查找元素。
- Bitmap Container:当内部存储的数据较多时使用此容器。具体来说,当存储超过 4096 个元素时使用。此容器由 65536 位(8192 字节)的 Bitmap 实现。每个比特表示该位置的整数是否存在。
- Run Container:当存储连续的整数范围时使用此容器。例如,当存储 1, 2, 3, 4, 5 等连续整数时,效率很高。此容器由 (起始值, 长度) 对的列表实现。如果所有值都存在?那意味着从头到尾都存在,只需存储 (0, 65536) 即可。
这种方式使得 Roaring Bitmap 能够非常有效地利用内存,并为各种数据密度提供最佳性能。
Roaring Bitmap 的功能
基本功能
Roaring Bitmap 提供以下基本功能。
- 插入:向 Bitmap 添加新的整数。在此过程中,可以选择合适的容器,或者在需要时转换容器。
- 删除:从 Bitmap 中移除现有整数。在此过程中,也会根据容器的状态进行适当的处理。
- 搜索:检查特定整数是否存在于 Bitmap 中。此过程执行速度非常快。
集合运算
Roaring Bitmap 还支持以下集合运算。
- 并集 (Union):创建包含两个 Bitmap 所有元素的新 Bitmap。
- 交集 (Intersection):创建仅包含两个 Bitmap 中都存在的元素的新 Bitmap。
- 对称差集 (Symmetric Difference):创建仅包含两个 Bitmap 中一个存在的元素的新 Bitmap。
这些运算根据 Bitmap 的容器类型进行了优化,执行速度非常快。
在 Go 语言中
在 Go 语言中,可以使用 github.com/RoaringBitmap/roaring
包轻松使用 Roaring Bitmap。此包提供了 Roaring Bitmap 的所有功能,并针对 Go 语言的特性进行了优化。
安装
1go get github.com/RoaringBitmap/roaring/v2
使用示例
1package main
2
3import (
4 "log"
5
6 "github.com/RoaringBitmap/roaring/v2"
7)
8
9func main() {
10 b1 := roaring.BitmapOf(1, 2, 3, 4) // 用 1, 2, 3, 4 初始化
11 b2 := roaring.BitmapOf(2, 4, 6, 8, 12) // 用 2, 4, 6, 8, 12 初始化
12
13 b1.Add(5) // 添加 5
14 b2.Add(10) // 添加 10
15
16 b2.Remove(12) // 移除 12
17
18 and := roaring.FastAnd(b1, b2)
19 or := roaring.FastOr(b1, b2)
20
21 // bitmap 运算会修改原数据,因此使用克隆副本
22 xor := b1.Clone()
23 xor.Xor(b2)
24
25 log.Printf("and result: %v", and.ToArray())
26 log.Printf("or result: %v", or.ToArray())
27 log.Printf("xor result: %v", xor.ToArray())
28}
12025/09/08 22:16:27 and result: [2 4]
22025/09/08 22:16:27 or result: [1 2 3 4 5 6 8 10]
32025/09/08 22:16:27 xor result: [1 3 5 6 8 10]
上述示例代码中未提及的主要功能包括:
Contains(x uint32) bool
:检查特定值是否存在于 Bitmap 中GetCardinality() uint64
:返回 Bitmap 中包含的元素数量Rank(x uint32) uint64
:返回小于或等于特定值的元素数量ToBytes() ([]byte, error)
:将 Bitmap 序列化为字节切片FromBytes(data []byte) error
:从字节切片恢复 BitmapClone() *Bitmap
:创建 Bitmap 的副本Clear()
:初始化 BitmapNextValue(x uint32) int64
:返回大于或等于 x 的下一个元素PreviousValue(x uint32) int64
:返回小于 x 的上一个元素Maximum() uint32
:返回 Bitmap 中最大的元素Minimum() uint32
:返回 Bitmap 中最小的元素
有关这些功能的详细信息,请参阅官方文档。
值得注意的是,roaring 包内部不仅支持 uint32,还包含支持 uint64 的包。它们提供几乎相同的接口,因此可以直接替换使用。
1package main
2
3import (
4 "log"
5
6 "github.com/RoaringBitmap/roaring/v2/roaring64"
7)
8
9func main() {
10 b1 := roaring64.BitmapOf(1, 2, 3, 4) // 用 1, 2, 3, 4 初始化
11 b2 := roaring64.BitmapOf(2, 4, 6, 8, 12) // 用 2, 4, 6, 8, 12 初始化
12
13 b1.Add(5) // 添加 5
14 b2.Add(10) // 添加 10
15
16 b2.Remove(12) // 移除 12
17
18 and := roaring64.FastAnd(b1, b2)
19 or := roaring64.FastOr(b1, b2)
20
21 // bitmap 运算会修改原数据,因此使用克隆副本
22 xor := b1.Clone()
23 xor.Xor(b2)
24
25 log.Printf("and result: %v", and.ToArray())
26 log.Printf("or result: %v", or.ToArray())
27 log.Printf("xor result: %v", xor.ToArray())
28}
总结
Roaring Bitmap 是一种强大的工具,可以高效地存储和操作高基数整数集合。在 Go 语言中,使用 github.com/RoaringBitmap/roaring
包可以轻松利用 Roaring Bitmap 的所有功能。该包通过各种容器类型和优化的运算,最大化了内存使用和性能。在数据库索引、日志分析、统计计算等各种领域,Roaring Bitmap 都可以用于实现高效的数据处理。
Roaring Bitmap 在大规模数据集上表现出卓越的性能,并可在各种应用程序中发挥作用。结合 Go 语言简洁高效的语法,Roaring Bitmap 为开发者提供了强大的工具。预计 Roaring Bitmap 将会不断发展,并带来更多功能和优化。
我为什么会做这个呢?为了在时序数据库中实现每个标签的索引和快速的并集运算。