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。
key:sign: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,这是最炸裂的优势。
流程
-
初始化一个 Bitmap,所有 bit 默认是 0
-
遍历原始数据,每读到一个 ID,把
offset=ID置为 1SETBIT big_id_set 12345 1 // ID=12345存在 -
全部导入完成后:
- 判断 ID 是否存在:
GETBIT big_id_set 12345,返回 1 存在、0 不存在 - 遍历整个 bitmap,把所有值 = 1 的 offset 全部输出 → 自动有序!
- 判断 ID 是否存在:
重点:你按 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
其他位运算对应的业务含义
BITOP AND交集:同时满足所有标签(A 并且 B)BITOP OR并集:满足任意一个标签(A 或者 B)BITOP XOR异或:只满足其中一个,不同时满足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 1AND 运算:
会员: 0 1 1 0 7天活跃: 0 0 1 1 ------------------- AND结果: 0 0 1 0结果里只有 offset=2 是 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、字符串),不能直接当 offset,Bitmap 内存会爆炸。
❌ 不适合:随机字符串、不连续超大 ID;这种场景优先布隆过滤器。
- BITOP 位运算属于重操作,超大 bitmap 会比较耗 CPU,要分片。
- Redis Bitmap 最大是 512MB,最多操作 2^32 个 bit 位。