【高频面试题】面试题里常出现的Bitmap是什么(附源码和多种场景) 📅 发布时间:2026/9/14 23:15:52 👁 浏览次数: Bitmap位图一句话定义Bitmap 就是位数组数组里每一个元素只有 0/11bit 只记录一个布尔状态Redis 的 Bitmap不是独立数据类型底层就是 String只是提供了位操作命令。offsetbit 的下标。offset10代表第 10 个 bit。 1 字节 8bit所以 1MB 可以记录 800 万条布尔标记内存极其省无任何误判结果 100% 精确。举个最简单例子 要记录数字0~7是否存在bit位置7 6 5 4 3 2 1 0 bit值 0 1 0 0 1 0 0 1bit01 → 数字 0 存在bit31 → 数字 3 存在bit61 → 数字 6 存在只需要1 个字节就能保存这 8 个数字的存在状态。 如果用数组存布尔值Java 里一个 boolean 占 1 字节就要 8 字节内存差 8 倍数字范围越大节省效果越恐怖。核心命令SETBIT key offset 1/0设置某一位GETBIT key offset读取某一位BITCOUNT key统计里面有多少个 1计数BITOP AND/OR/XOR多个 bitmap 做位运算求交集、并集✅ 典型业务场景1. 用户签到最经典key sign:20260912offset 用户 ID签到就把该 bit 置 1。keysign:20260912字符串标识这是哪一天的签到位图offset用户 IDbit 下标比如用户 ID100offset100命令# 用户100今天签到把第100位设为1 SETBIT sign:20260912 100 1 # 查询用户100今天有没有签到 GETBIT sign:20260912 100 # 统计今天签到总人数 BITCOUNT sign:20260912✅ 理解key用来区分「哪一组位图数据」offset位图内部第几个 bit 位BITCOUNT 直接算出今日签到总人数多天 bitmap 做BITOP AND统计连续签到用户优势一个亿用户一天签到记录只需要约 12MB 内存2. 日活用户统计、在线用户标记当日活跃用户 ID 作为 offset访问一次就 SETBIT1一天一个 key。日活 DAUkey dau:20260912 offsetuserId用户维度记录该用户的不同权限key user:perm:10001 offset 0查看1新增2删除keyuser:perm:10001Redis 里这个 key 代表用户 ID10001 的权限位图一个用户对应一条 Bitmap。offset 就是 bit 的位置第几位每一个 bit 代表一种权限offset0 → 第 0 位 bit查看权限offset1 → 第 1 位 bit新增权限offset2 → 第 2 位 bit删除权限bit 的值1拥有该权限0没有该权限举个实例用户 10001拥有【查看】、【新增】没有删除权限offset0 1 ✅ 查看offset1 1 ✅ 新增offset2 0 ❌ 删除执行 Redis 命令SETBIT user:perm:10001 0 1 SETBIT user:perm:10001 1 1 SETBIT user:perm:10001 2 0这个 key 对应的二进制011bit2 bit1 bit0判断权限怎么用判断用户 10001 有没有删除权限GETBIT user:perm:10001 2返回 0 → 无删除权限返回 1 → 拥有。人群标签近 7 天浏览商品的用户key tag:visit_7d offsetuserId3. 海量整数 ID 去重、大数据快速排序离线大数据场景比如找出 1 亿以内哪些 ID 存在。核心思路ID 范围0 ~ 100,000,0001 亿以内整数 ID 用一个 Bitmap位数组1 个 bit 代表 1 个 IDbit 下标 IDbit1 代表该 ID 存在bit0 代表不存在。内存估算1 亿个 bit 100,000,000 / 8 12.5MB✅ 1 亿个 ID只需要 12.5MB这是最炸裂的优势。流程初始化一个 Bitmap所有 bit 默认是 0遍历原始数据每读到一个 ID把offsetID置为 1SETBIT big_id_set 12345 1 // ID12345存在全部导入完成后判断 ID 是否存在GETBIT big_id_set 12345返回 1 存在、0 不存在遍历整个 bitmap把所有值 1 的 offset 全部输出 →自动有序重点你按 bit 下标从小到大遍历输出出来的 ID 天然就是升序直接完成排序 去重一步搞定。为什么能同时【去重 排序】去重同一个 ID 多次出现反复 SETBIT 置 1结果还是 1自动去重重复写入不影响。排序bit 的 offset 本身就是 ID从 0→1 亿顺序扫描 bit 数组读到 1 就输出 ID输出天然有序不需要额外排序算法。适用前提✅ ID 必须是稠密、连续范围的整数且最大值可控0~1 亿这种最大值是 1 亿bitmap 长度就只需要开到 1 亿 bit。要求 ID 是稠密整数用户 ID、订单 ID 这种连续数字❌ 不适合 ID 是稀疏、很大、跨度极大。比如 ID 是1、1000000000只有两个 ID但最大 offset 是 10 亿需要 125MB如果最大 ID 到几十亿内存直接爆炸。4. 标签筛选人群多个人群 bitmap 做位运算快速筛选同时满足多个标签的用户比如会员并且近 7 天活跃核心每个标签单独一个 Bitmap用按位与 AND求交集同时满足多个标签。设计思路每一个标签单独一个 Redis Bitmap 的 keyoffset 用户 IDbit1 代表该用户拥有这个标签0 代表没有keytag:is_member // 标签是否会员 keytag:active_7d // 标签近7天活跃tag:is_member用户是会员 → bit1tag:active_7d用户近 7 天活跃 → bit1目标同时满足两个标签会员 AND 7 天活跃用BITOP AND做按位与运算按位与规则两个 bit 都为 1结果才是 1只要有一个 0结果就是 0正好对应A 条件并且B 条件Redis 命令# 把 tag:is_member 和 tag:active_7d 做按位与结果存入新bitmap tag:result BITOP AND tag:result tag:is_member tag:active_7d # 统计符合条件总人数 BITCOUNT tag:resulttag:result就是筛选出来的人群位图bit1 代表用户同时拥有两个标签后续遍历这个 result 的 bit就能拿到所有符合条件的 userId多标签扩展如果要会员并且近 7 天活跃并且有下单记录BITOP AND tag:result tag:is_member tag:active_7d tag:has_order其他位运算对应的业务含义BITOP AND交集同时满足所有标签A 并且 BBITOP OR并集满足任意一个标签A 或者 BBITOP XOR异或只满足其中一个不同时满足BITOP NOT取反不满足该标签示例OR会员 或者 近 7 天活跃用户BITOP OR tag:result tag:is_member tag:active_7d举个极简小例子方便理解用户 ID1、2、3、4tag:is_member (会员)用户 1、2 是会员 → bit11bit21 →0 1 1 0tag:active_7d (7 天活跃)用户 2、3 活跃 → bit21bit31 →0 0 1 1AND 运算会员: 0 1 1 0 7天活跃: 0 0 1 1 ------------------- AND结果: 0 0 1 0结果里只有 offset2 是 1 →只有用户 2 同时是会员 7 天活跃✅ 优点性能极高位运算底层是二进制批量计算亿级用户圈人远快于数据库 SQL join内存占用极低多标签组合筛选非常灵活随便组合 AND/OR。⚠️ 缺点 面试坑点BITOP 会生成新的 bitmap 结果占用内存如果 bitmap 很大这个操作非常消耗 CPU。Redis 做大 bitmap 位运算会阻塞线上高并发不建议实时跑一般离线预计算。前提用户 ID 必须是非负稠密整数用户 ID 是 UUID / 字符串无法直接当 offset。Redis 不适合超大规模人群圈选。大数据平台ClickHouse、Spark一般用RoaringBitmap压缩位图专门优化稀疏人群场景内存更小、计算更快。⚠️ Bitmap 的短板面试高频坑offset 不能太大适合稠密整数 ID如果 ID 非常稀疏比如随机 UUID、字符串不能直接当 offsetBitmap 内存会爆炸。❌ 不适合随机字符串、不连续超大 ID这种场景优先布隆过滤器。BITOP 位运算属于重操作超大 bitmap 会比较耗 CPU要分片。Redis Bitmap 最大是 512MB最多操作 2^32 个 bit 位。