【高频面试题】面试题里常出现的Bitmap是什么(附源码和多种场景)

Bitmap(位图)一句话定义

Bitmap 就是位数组 ,数组里每一个元素只有 0/1,1bit 只记录一个布尔状态 ;Redis 的 Bitmap不是独立数据类型,底层就是 String,只是提供了位操作命令

offset:bit 的下标。offset=10 代表第 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 1
  • bit0=1 → 数字 0 存在
  • bit3=1 → 数字 3 存在
  • bit6=1 → 数字 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:20260912,offset = 用户 ID;签到就把该 bit 置 1。

  • keysign:20260912(字符串,标识这是哪一天的签到位图)
  • offset:用户 ID(bit 下标,比如用户 ID=100,offset=100)

命令:

复制代码
# 用户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,访问一次就 SETBIT=1;一天一个 key。

日活 DAU

复制代码
key = "dau:20260912"
offset=userId

用户维度,记录该用户的不同权限

复制代码
key = "user:perm:10001"
offset 0=查看,1=新增,2=删除
  • key="user:perm:10001":Redis 里这个 key 代表用户 ID=10001 的权限位图 ,一个用户对应一条 Bitmap
  • offset 就是 bit 的位置(第几位) ,每一个 bit 代表一种权限:
    • offset=0 → 第 0 位 bit:查看权限
    • offset=1 → 第 1 位 bit:新增权限
    • offset=2 → 第 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 对应的二进制:011(bit2 bit1 bit0)

判断权限怎么用?

判断用户 10001 有没有删除权限:

复制代码
GETBIT user:perm:10001 2

返回 0 → 无删除权限;返回 1 → 拥有。

人群标签:近 7 天浏览商品的用户

复制代码
key = "tag:visit_7d"
offset=userId

3. 海量整数 ID 去重、大数据快速排序

离线大数据场景,比如找出 1 亿以内哪些 ID 存在。

核心思路

ID 范围:0 ~ 100,000,000(1 亿以内整数 ID) 用一个 Bitmap(位数组),1 个 bit 代表 1 个 ID bit 下标 = ID;bit=1 代表该 ID 存在,bit=0 代表不存在。

内存估算

1 亿个 bit = 100,000,000 / 8 = 12.5MB ✅ 1 亿个 ID,只需要 12.5MB,这是最炸裂的优势。

流程

  1. 初始化一个 Bitmap,所有 bit 默认是 0

  2. 遍历原始数据,每读到一个 ID,把offset=ID置为 1

    复制代码
    SETBIT big_id_set 12345 1  // ID=12345存在
  3. 全部导入完成后:

    • 判断 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 的 key

  • offset = 用户 ID,bit=1 代表该用户拥有这个标签,0 代表没有

    key=tag:is_member // 标签:是否会员
    key=tag:active_7d // 标签:近7天活跃

  • tag:is_member:用户是会员 → bit=1

  • tag:active_7d:用户近 7 天活跃 → bit=1

目标:同时满足两个标签(会员 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:result
  • tag:result 就是筛选出来的人群位图,bit=1 代表用户同时拥有两个标签
  • 后续遍历这个 result 的 bit,就能拿到所有符合条件的 userId

多标签扩展

如果要:会员 并且 近 7 天活跃 并且 有下单记录

复制代码
BITOP AND tag:result tag:is_member tag:active_7d tag:has_order

其他位运算对应的业务含义

  1. BITOP AND 交集:同时满足所有标签(A 并且 B)
  2. BITOP OR 并集:满足任意一个标签(A 或者 B)
  3. BITOP XOR 异或:只满足其中一个,不同时满足
  4. BITOP NOT 取反:不满足该标签

示例:

  • OR:会员 或者 近 7 天活跃用户

    BITOP OR tag:result tag:is_member tag:active_7d

举个极简小例子(方便理解)

用户 ID:1、2、3、4

  • tag:is_member (会员):用户 1、2 是会员 → bit1=1,bit2=1 → 0 1 1 0
  • tag:active_7d (7 天活跃):用户 2、3 活跃 → bit2=1,bit3=1 → 0 0 1 1

AND 运算:

复制代码
会员:        0 1 1 0
7天活跃:     0 0 1 1
-------------------
AND结果:     0 0 1 0

结果里只有 offset=2 是 1 → 只有用户 2 同时是会员 + 7 天活跃

✅ 优点

  1. 性能极高:位运算底层是二进制批量计算,亿级用户圈人远快于数据库 SQL join;
  2. 内存占用极低;
  3. 多标签组合筛选非常灵活,随便组合 AND/OR。

⚠️ 缺点 & 面试坑点

  1. BITOP 会生成新的 bitmap 结果,占用内存 ;如果 bitmap 很大,这个操作非常消耗 CPU。Redis 做大 bitmap 位运算会阻塞,线上高并发不建议实时跑,一般离线预计算。
  2. 前提:用户 ID 必须是非负稠密整数;用户 ID 是 UUID / 字符串无法直接当 offset。
  3. Redis 不适合超大规模人群圈选。大数据平台(ClickHouse、Spark)一般用RoaringBitmap(压缩位图),专门优化稀疏人群场景,内存更小、计算更快。

⚠️ Bitmap 的短板(面试高频坑)

  1. offset 不能太大,适合稠密整数 ID 如果 ID 非常稀疏(比如随机 UUID、字符串),不能直接当 offset,Bitmap 内存会爆炸。

❌ 不适合:随机字符串、不连续超大 ID;这种场景优先布隆过滤器。

  1. BITOP 位运算属于重操作,超大 bitmap 会比较耗 CPU,要分片。
  2. Redis Bitmap 最大是 512MB,最多操作 2^32 个 bit 位。
相关推荐
学习智者1 小时前
《玄》IDE v3.8.2重磅发布:数据外置+全链路优化
开发语言·c++·ide·中文语言 玄
benbenAItalk9 小时前
数字人口播视频的批量生产实践:素材规范、任务编排与质量验收
java·前端·音视频
sunshine22 girl9 小时前
Java学习二 基本语法2, 算术运算符
java·学习
2601_9669496510 小时前
为什么量化策略需要大量历史股票数据?从回测可信度理解数据规模
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
niucloud-admin10 小时前
JAVA V6 多商户商城 开发文档——插件目录结构
java·开发语言
2601_9628857210 小时前
如何用 Python 扫描 A 股跳空缺口并统计缺口回补概率?
java·前端·python
滕州市燕猫虎计算机科技工作室个体工商户10 小时前
Java锁
java·开发语言
多加点辣也没关系10 小时前
JavaScript|第31章:表单与控件
开发语言·javascript·ecmascript
怕浪猫10 小时前
长会话不崩盘:DeepSeek Harness 的上下文压缩与目标管理策略
面试·github·agent