Redis位图
1. 前言
在平常的开发中,或有一些bool型数据需要存储,比如用户一年的签到记录,签了就记录为1,没签就记录为0,若要记录365天,只需要365bit(46Byte)个空间就可以实现。
位图不是特殊的数据结构,它的内容其实就是普通的字符串,也就是byte数组。可以使用get/set
直接获取和设置整个位图的内容,也可以使用getbit/setbit
将byte数组看成位数组来处理。
> setbit <key> <index> <value> # 设置第index位为value
> setbit s 2 1
注意,Redis的位图,较低位在左,较高位在右边。
2.基本用法
Redis的位数组是自动扩展的,如果设置的某个偏移量超出了现有的内容范围,Redis就会自动将位数组进行零扩充。
如果对应位字节是不可打印字符,redis-cli会显示该字符的十六进制形式。
统计和查找
Redis提供了位图统计指令bitcount
和位图查找指令bitpos
.
bitcount 用来统计指定位置范围内的1的个数
bitpos 用来查找指定范围内出现的第一个0或者1
例如,可以通过bitcount
来查找用户一共签到了多少天, 用bitpos来查找用户从哪一天开始第一次签到的。如果指定了范围[start, end],就可以统计在某个时间范围内用户签到了多少天,用户第一次签到的时间。
但是需要注意的是,start和end参数是字节(Byte)索引,所以指定的位范围必须是8的倍数。
> set w hello
OK
> bitcount w
(integer) 21
> bitcount w 0 0 # 第0个字符中1的个数
(integer) 3
# bitpos <0 | 1> [<start> <end>]
> bitpos 1 0 # 第0个字符起,第一个1的位
(integer) 1
> bitpos 1 1 1
(integer) 9
3. bitfield
前文中的setbit
和getbit
指定位的值都是单个位的,如果一次要操作多个位,就必须用管道来处理。Redis3.2版本以后新增了一个功能强大的指令bitfield
,有这个指令后,不用管道也可以一次进行多个位操作。
- bitfield有三个子指令,分别是
get
,set
,incrby
,他们都可以对指定位片段进行读写,但是最多只能处理64个连续的位。如果超过64位。
> set w hello
OK
> bitfield w get u4 0 # 从0号位开始获取4个bit, 结果用无符号整数显示 (u)
6
> bitfield w get i3 2 # 从2号位开始,获取3个bit, 结果用有符号整数显示(i)
-3
注意,有符号数最多可以获取64位,无符号最多可以只能获取63位,因为Redis协议中的Integer是有符号数,最大64位,不能传递64位无符号值。如果超出位数限制,Redis就会报参数错误。
- bitfield可以一次执行多个指令。
> bitfield w get u4 0 get u3 2 get i4 0 get i3 2
1) (integer) 6
2) (integer) 5
3) (integer) 6
4) (integer) -3
- 使用set指令将第二个字符
e
改成a
, a的ASCII码为97
>bitfield w set u8 8 97 # 从8号位开始,将接下来的8个bit用无符号97的二进制替换。
1) (integer) 101
> get w
"hallo"
-
incrby
用来对指定范围的位进行自增操作,既然存在自增,就会出现溢出的问题。若自增出现溢出问题,Redis默认将溢出的符号位丢掉,如果是有符号数127,则加1后溢出编程-128;如果是无符号数255,则加1后溢出变为0。
> set w hello
OK
>bitfield w incrby u4 2 1 # 从2号位开始,对后面的4个bit位进行加1的操作
(integer) 11 # 返回修改后的位 11 = (1011)b
bitfield指令提供了溢出策略子指令overflow
,用户可以选择溢出行为,默认是wrap(隐藏),其他行为包括fail
(报错不执行),sat
(饱和截断,超过范围就停留在最大值或者最小值)。
overflow指令只是影响接下来的第一条指令,这条指令执行完后,溢出策略还是会变为默认值wrap
.
> bitfield w overflow fail incrby u4 2 1 # 设置溢出策略为fail
> bitfield w overflow sat incrby u4 2 1 # 设置溢出策略为sat