介绍
Bitmap(位图)是一串连续的二进制数组,可以通过偏移量(offset)定位元素。BitMap 通过最小的单位 bit 来进行 0 | 1
设置,表示某个元素的值或者状态,时间复杂度为 O(1)
bit 是计算机中最小的单位,使用它进行储存将非常节省空间,特别适合一些数据量大且使用二值统计(取值 0 或 1)的场景
内部实现
Bitmap 是用 String 类型作为底层数据结构实现的
String 类型是会保存为二进制的字节数组,Bitmap 把字节数组的每个 bit 位利用起来,用来表示一个元素的二值状态,可以把 Bitmap 看作是一个 bit 数组
常用命令
基本操作:
# 设置值,其中 value 只能是 0 和 1
SETBIT key offset value
# 获取值
GETBIT key offset
# 获取指定范围内值为 1 的个数,start 和 end 以字节为单位
BITCOUNT key start end
运算操作:
# operations 位移操作符,枚举值:
# AND 与运算 &
# OR 或运算 |
# XOR 异或 ^
# NOT 取反 ~
# result 计算的结果,会存储在该 key 中
# key1 ... keyn 参与运算的 key,可以有多个,空格分割,not 运算只能一个 key
# 当 BITOP 处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作 0
# 返回值是保存到 result 的字符串的长度(以字节为单位),和输入 key 中最长的字符串长度相等
BITOP [operations] [result] [key1] [keyn…]
# 返回指定 key 中第一次出现指定 value(0/1) 的位置
BITPOS [key] [value]
应用场景
非常适合二值状态统计的场景,在记录海量数据时,能够有效节省内存空间
签到统计
在签到打卡的场景中,我们只用记录签到(1)或未签到(0),所以它就是非常典型的二值状态。
签到统计时,每个用户一天的签到用 1 个 bit 位就能表示,一个月(假设是 31 天)的签到情况用 31 个 bit 位就可以,而一年的签到也只需要用 365 个 bit 位,根本不用太复杂的集合类型。
假设我们要统计 ID 100 的用户在 2022 年 6 月份的签到情况,就可以按照下面的步骤进行操作。
第一步,执行下面的命令,记录该用户 6 月 3 号已签到。
# uid 100 的用户 2022 年 6 月 3 号已签到(偏移量为 2 代表 3 号)
SETBIT uid:sign:100:202206 2 1
# 统计该用户在 6 月份的签到次数
BITCOUNT uid:sign:100:202206
# BITPOS key bitValue [start] [end]
# 返回数据表示 Bitmap 中第一个值为 bitValue 的 offset 位置
# 获取这个月首次打卡时间
BITPOS uid:sign:100:202206 1
# 因为 offset 从 0 开始,所以需要将返回的 value + 1
判断用户登陆态
# 登录
SETBIT login_status 10086 1
# 检查该用户是否登陆,1:已登录;0:未登录
GETBIT login_status 10086
# 登出
SETBIT login_status 10086 0
连续签到用户总数
如何统计出连续 7 天打卡用户总数?
把每天的日期作为 Bitmap 的 key,userId 作为 offset,若是打卡则将 offset 位置的 bit 设置成 1,则集合的每个 bit 位就是一个用户在该日期的打卡记录
一共有 7 个这样的 Bitmap,对这 7 个 Bitmap 对应的 bit 位做「与」运算,运算后结果是 1 就说明该用户 7 天连续打卡
结果保存到一个新 Bitmap 中,再通过 BITCOUNT
统计 bit = 1 的个数便得到了连续打卡 7 天的用户总数
# 与操作
BITOP AND destmap bitmap:1 bitmap:2 bitmap:3 bitmap:4 bitmap:5 bitmap:6 bitmap:7
# 统计 bit 位 = 1 的个数
BITCOUNT destmap
即使有一亿个用户,1 个 Bitmap 占用的内存也不大,大约 12 MB(10^8 / 8 / 1024 / 1024)。同时最好给 Bitmap 设置过期时间,让 Redis 删除过期的打卡数据,节省内存