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