当前位置: 首页 > news >正文

数据结构 Bitmap(位图)完整详解

数据结构 Bitmap(位图)完整详解

一、什么是 Bitmap?

Bitmap(位图)是一种基于位的数组结构,使用每一个 bit 位来存储一个二元状态(0/1,true/false,存在/不存在)。它将一个范围(如整数 ID、枚举值)直接映射到内存中的某个 bit 位置,从而实现极高的空间效率和快速的位置访问。

核心思想:用 1 个 bit 代替原本需要 1 个 byte(甚至更多)才能存储的布尔信息。


二、核心原理

假设我们要记录0n-1共 n 个元素的存在性。传统做法是用一个boolean[],每个布尔值在大多数语言中占用 1 字节(Java)甚至 4 字节(C# bool)。而 Bitmap 将 n 个 bit 连续存储,这些 bit 被组织成若干个字节(或字)。

映射关系:

  • k个 bit 对应数字k的状态。
  • 内存中第i个字节的第j位(通常 j 从 0 到 7,从低位到高位或相反):
    • 字节索引byteIndex = k / 8
    • 位偏移bitOffset = k % 8

访问公式:

  • 读取:(bytes[byteIndex] >> bitOffset) & 1
  • 设置 1:bytes[byteIndex] |= (1 << bitOffset)
  • 设置 0:bytes[byteIndex] &= ~(1 << bitOffset)
  • 翻转:bytes[byteIndex] ^= (1 << bitOffset)

三、基本操作

操作描述时间复杂度
set(k)将第 k 位设为 1O(1)
clear(k)将第 k 位设为 0O(1)
test(k)返回第 k 位的值O(1)
count()统计所有 1 的个数O(n) 或硬件加速
bitwise AND/OR/XOR/NOT两个 bitmap 之间的集合运算O(n)
nextSetBit(from)从某个位置向后找第一个 1O(n) 或通过分段优化

位运算操作是 bitmap 相对于其他集合(如 HashSet)的独特优势,可以极快地完成并集、交集、差集,非常适合批处理和索引合并。


四、空间占用与计算

  • 理论最小内存size_in_bits = max_element + 1时,内存占用 =ceil(size_in_bits / 8)字节。

  • 实例

    • 记录 0~99 共 100 个数 → 100 bits ≈ 13 字节
    • 记录 0~10 亿(10^9) → 10^9 bits ≈ 119 MB
    • 记录 0~2^32-1(约 43 亿) → 512 MB
  • 对比

    数据结构存储 1 亿个布尔值内存
    boolean[](Java)100M × 1 字节100 MB
    BitSet(Java)100M bits12.5 MB
    HashSet<Integer>(存储存在的那些值)若存在 5000 万个数,每个 Integer 对象+指针≈ 1.2 GB+

但注意:bitmap 的空间开销与值域范围成正比,与实际元素个数无关。如果值域稀疏(如只有 10 个元素,但它们的值在 0~10^9 之间),单纯 bitmap 会非常浪费。此时需用稀疏优化方案(Roaring Bitmap)。


五、实现细节(以 Java 和 Python 为例)

Javajava.util.BitSet的内部实现
  • 内部使用long[] words,每个 long 存储 64 个 bit。
  • 动态扩容:当需要的索引超出当前大小时,自动创建更大的数组。
  • 提供了丰富的集合运算:and,or,xor,andNot
BitSetbs=newBitSet();bs.set(100);// 设置第100位为1bs.set(200,300);// 设置区间 [200,300) 为1booleanexist=bs.get(100);// truebs.clear(100);// 清除intnext=bs.nextSetBit(0);// 找到第一个1的位置
Python 使用bytearray实现简易 bitmap
classBitmap:def__init__(self,size):self.size=size self.bytes=bytearray((size+7)//8)defset(self,k):ifk>=self.size:raiseIndexError self.bytes[k>>3]|=1<<(k&7)defclear(self,k):self.bytes[k>>3]&=~(1<<(k&7))deftest(self,k):return(self.bytes[k>>3]>>(k&7))&1defcount(self):# bin(x).count('1') 效率低,可查表或使用 int.bit_count() (Python 3.8+)returnsum(b.bit_count()forbinself.bytes)

六、优点与局限性

✅ 优点
  1. 极致空间效率:比基于字节的布尔数组节省 8~32 倍内存。
  2. CPU 缓存友好:连续内存布局,批量操作时高速缓存命中率高。
  3. 并行/向量化:一次 64 位运算可同时处理 64 个元素(SIMD 友好)。
  4. 集合运算极快:交集、并集只需逐字进行位运算,时间复杂度 O(N/word_size)。
❌ 局限性
  1. 固定值域:需要预先知道最大元素范围,范围过大而元素稀疏时浪费严重。
  2. 只能存储二元状态:不能直接存储关联数据(如权重、附加属性)。
  3. 非动态稀疏:若最大 ID 上亿但只有几千个元素,512 MB 内存远高于红黑树或哈希表。
  4. 计数与遍历:统计 1 的个数(count)需要扫描整个位图(O(内存大小)),除非用硬件popcount指令加速,但扫描依然需要遍历所有 bits。

七、高级变体:解决稀疏问题

为解决“值域大而元素少”时的内存浪费,学术界和工业界提出了多种压缩 bitmap 结构,其中最著名的是Roaring Bitmap

Roaring Bitmap 核心思想
  • 将 32 位整数范围按高 16 位分成 2^16 个桶(chunk),每个桶最多包含 65536 个可能的低 16 位值。
  • 桶内根据元素密度选择不同容器:
    • Array Container:稀疏时(元素数 < 4096),存储有序 16 位整数数组,内存约 2 字节/元素。
    • Bitmap Container:稠密时(元素数 ≥ 4096),使用 8KB 的位图(65536 bits = 8KB),内存固定。
    • Run Container(可选):对连续区间(如 [100,200])使用 RLE 编码,空间更优。

效果:对随机分布的元素,Roaring 比普通 bitmap 节省 90% 以上内存,且交集、并集性能仍然极高。已被广泛应用于:Spark、Druid、ClickHouse、Lucene 等。

其他变体
  • EWAH (Compressed Word-Aligned Bitmap):使用运行长度编码,适合数据库。
  • Concise:对连续 0/1 压缩,查询略慢。
  • JavaBitSet的简单扩展:不支持压缩,仅固定长度。

八、典型应用场景深度剖析

应用领域具体说明为何用 Bitmap
海量整数判存(布隆过滤器底层)如网页 URL 去重、垃圾邮件过滤。布隆过滤器使用多个 bitmap 和多个哈希函数。极低内存 + 可容忍小概率误判。
用户签到/活跃统计每个用户一条记录,每天一个 bit 表示是否签到。1 亿用户 × 365 天 = 365 亿 bits ≈ 4.3 GB,比传统方式小几十倍。按天按位运算,快速统计连续签到天数等。
数据库位图索引对低基数列(性别、地区、产品类别)建立位图索引,每个值对应一个 bitmap,标记哪些行具有该值。适合 OLAP 多维分析。支持快速 AND/OR 多条件过滤,空间远小于 B-Tree。
权限系统使用 32 位整数作为权限掩码,每个 bit 代表一种权限(读、写、删除、审核…)。位运算组合权限,操作简单,存储仅 4 字节/用户。
磁盘块/内存页管理操作系统的空闲块位图,每个 bit 表示磁盘块或内存页是否空闲。内存小,查询和修改极快。
图着色/状态标记BFS/DFS 中记录节点是否访问过,当节点数达百万级以上时,bitmap 比 bool 数组节省内存。节省内存,避免 OOM。
倒排索引压缩搜索引擎中 docID 列表可用 bitmap 或 Roaring 存储,用于快速求交集(多关键词搜索)。位图压缩 + 高性能集合运算。
实时风控/反作弊记录用户在最近 N 小时内是否触发某个事件(如 24 小时滑动窗口),每个时间片用一位。滑动窗口可通过移位和位运算实现,高效无锁。

九、性能考量与优化技巧

  1. 计数优化:使用 CPU 提供的POPCNT指令(通过Long.bitCountint.bit_count)硬件加速,比逐位检查快 10 倍以上。
  2. 循环遍历所有 1:不要逐位扫描,使用nextSetBit跳跃到下一个 1,内部实现通常按字查找。
  3. 批量操作:尽可能使用andor等矢量操作,而不是对每个 bit 单独循环。
  4. 选择合适的变体
    • 稠密、值域固定 → 普通 bitmap
    • 值域很大(> 2^31)但分布未知 → Roaring Bitmap
    • 需要持久化到磁盘 → 考虑 EWAH 或 Roaring 的序列化格式
  5. 字节对齐:在 C/C++ 中访问时使用uint64_t对齐,避免跨字访问开销。

十、总结

特性描述
本质以 bit 为基本单位的数组,映射整数到状态
内存N bits 存储 N 个二元值,约 N/8 字节
速度单点 O(1),批量运算 O(N/字长)
最佳适用值域紧凑、元素稠密或需要高速集合运算的场景
稀疏优化Roaring Bitmap 等压缩结构

Bitmap 是计算机科学中“用空间换时间”的反面——用极致的空间压缩换取大量内存节省,同时在批量集合运算上甚至超过传统结构。理解 bitmap,不仅是掌握一个数据结构,更是学会如何用位这一最底层的单元去表示海量信息,这是高级系统设计和大数据处理的基石之一。

http://www.jsqmd.com/news/867986/

相关文章:

  • 2026年5月更新:福建地区如何联系专业钢丝绳输送带供应商——保定鼎基输送机械有限公司 - 2026年企业推荐榜
  • 2026年5月更新:徐州地区专业分选机销售与技术服务商深度解析 - 2026年企业推荐榜
  • 2026年5月眼镜行业升级,这家注塑机厂家凭何脱颖而出? - 2026年企业推荐榜
  • FAST-LIO 技术解析:原理、改进与开源实现
  • 2026年至今深圳冷链车市场深度解析:如何选择一家具备全生命周期服务能力的4S店? - 2026年企业推荐榜
  • 仓库管理软件核心功能拆解:企业如何利用仓库管理软件解决库存积压与错发难题
  • 通过TaotokenCLI工具一键配置开发环境与多工具密钥教程
  • iPhone17护眼钢化膜选购指南:6条护眼习惯+一柔一清技术解读
  • 影刀RPA跨境店群运营架构:Python高并发协同与Chromium环境隔离系统实战
  • Habitat具身智能仿真平台完全入门:从Sim到Lab,从环境搭建到配置详解
  • 英国论文AI降重总踩坑?4款常用工具整理
  • 假论文堆出多少假教授
  • ChatGPT API文档生成必须绕开的4个幻觉陷阱:附可验证的Prompt工程Checklist(含GitHub实测Repo)
  • 2026 DBA实测推荐:5款数据库管理工具 监控、SQL审核、AI能力横评
  • 618洗衣机能便宜多少?内衣洗衣机精选十大品牌!海尔/希亦等十款618闭眼入的内衣洗衣机~
  • Taotoken控制台功能导览,从密钥管理到用量分析的全流程操作
  • alias/bashrc
  • 西瓜(Citrullus lanatus)遗传转化服务选择指南:5大核心标准与伯远生物技术优势解析
  • 如何开启虚拟机共享文件夹
  • 【英飞凌 TriCore 实战】TC33x 存储体系全解:从 Fast/Slow RAM 到 Flash 刷写
  • Perplexity奖学金搜索失效真相,深度解析算法偏见、地域屏蔽与申请窗口期错配三大陷阱
  • C++ 中的矩阵介绍:以二维矩阵查找为例
  • 解密Palantir系列一:2. 传统软件的三大断裂
  • 人机这个二体问题背后往往隐藏着人机环境三体问题
  • 人机协同的五个典型特征
  • 全球眼用缓释药市场调查:预计2032年将攀升至25.46亿美元
  • Git 死亡三连实录:pull 冲突 → push 被拒 → merge 炸锅,完整抢救指南
  • 以源码方式使用pip install安装时报错ModuleNotFoundError: No module named ‘tomli‘
  • 4米2蓝牌飞翼车为啥买不到
  • C++ STL 双端队列 deque 详细介绍