1. 场景:

记录用户一年的签到记录,如果用String存储,需要使用365个key/value,操作起来麻烦 ,通过位图可以有效的简化这个操作。 它的统计很简单:如签到记为1,没有签到记为0,每天的记录占一个位:

1011011111  这样大概需要46个字节(365天占365位,一个字节8位),可以有效的节省存储空间。如果有一天想要统计用户一共签到的天数,只需要统计1的位数即可。

对于位图的操作,可以直接操作对应的字符串的(get/set),可以直接操作位(getbit/setbit).

2. 基本操作

Redis的基本操作可以归为两大类:

2.1. 零存整取

例如存储一个java字符串:(存储时是一个一个的,取出的时候按字符串一次取出)

            

接下来去存储:

                                            

2.2. 整存零取

存一个字符串进去,但通过位操作获取字符串。

3.统计

例如签到记录:

0111001111

1表示1签到的天,0表示没签到,统计总签到的天数,可以使用 bitcount :

127.0.0.1:6379> BITCOUNT name
(integer) 14

bitcount中,可以统计的起始位置,但是注意,这个起始位置是指字符串的起始位置,而不是bit的起始位置。

127.0.0.1:6379> BITCOUNT name 0 0
(integer) 3
127.0.0.1:6379> BITCOUNT name 0 1
(integer) 6
127.0.0.1:6379>

除了bitcount之外,还有一个bitpos。 bitpos可以用来统计在指定范围内出现的第一个 1 或者 0 的位置,而这个命令的起始位置和结束位置都是字符索引,不是bit索引,需要注意。

name为Java

# 获取第一个 1的位置, 可见Java中 位数据  01001010 01100001 01110110 01100001
127.0.0.1:6379>  BITPOS name 1
(integer) 1
# 获取第一个 0 的位置
127.0.0.1:6379>  BITPOS name 0
(integer) 0
# 获取第一个 0 的位置 (从第一个字符开始,到第一个字符结束), 对应于a的第一个0的位置
127.0.0.1:6379>  BITPOS name 0 1 1
(integer) 8

4.Bit批处理

在redis3.2之后,新加了一个功能叫做bitfield, 可以对bit进行批量操作。

例如:

BITFIELD name get u4 0

表示获取 name 中的位,从 0 开始获取,获取 4 位,返回一个无符号的数字。

  • u 表示无符号的数字

  • i 表示有符号的数字,有符号的话,第一个符号就表示符号位,1 表示一个负数。


127.0.0.1:6379> BITFIELD name get u4 0
1) (integer) 4
127.0.0.1:6379> BITFIELD name get i4 1
1) (integer) -7
127.0.0.1:6379> BITFIELD name get u4 1
1) (integer) 9
127.0.0.1:6379>


bitfield 也可以一次执行多个操作。

GET:

127.0.0.1:6379> BITFIELD name get u4 1 get i4 1 get u4 0 get i4 0
1) (integer) 9
2) (integer) -7
3) (integer) 4
4) (integer) 4
127.0.0.1:6379>

SET:

用无符号的 98 转换成的8位二进制数字,代替从第 8 位开始接下来的 8 位数字。

127.0.0.1:6379> get name
"Java"
127.0.0.1:6379> BITFIELD name set u8 8 98
1) (integer) 97
127.0.0.1:6379> get name
"Jbva"
127.0.0.1:6379>

INCRBY:

对指定范围进行自增操作,自增操作可能会出现溢出,既可能是向上溢出,也可能是向下溢出。 Redis中对于溢出的处理方案是折返。 8位无符号位数 255 加 1 溢出变为0; 8位有符号数127,加1 变为 -128。

127.0.0.1:6379> get name
"Java"
# 01001010 从 6 开始对接下来的2位无符号数加 1, 变成 01001011, 75, 对应K
127.0.0.1:6379> BITFIELD name incrby u2 6 1
1) (integer) 3
127.0.0.1:6379> get name
"Kava"
# 01001011 从 6 开始对接下来的2位无符号数加 1, -- 会溢出, 变成0100100, 72, 对应H 
127.0.0.1:6379> BITFIELD name incrby u2 6 1
1) (integer) 0
127.0.0.1:6379> get name
"Hava"
127.0.0.1:6379>

也可以修改默认的溢出策略,可以改为fail,表示执行失败:

127.0.0.1:6379> get name
"Hava"
127.0.0.1:6379> BITFIELD name overflow fail incrby u2 6 1
1) (integer) 1
127.0.0.1:6379> BITFIELD name overflow fail incrby u2 6 1
1) (integer) 2
127.0.0.1:6379> get name
"Java"
127.0.0.1:6379> BITFIELD name overflow fail incrby u2 6 1
1) (integer) 3
127.0.0.1:6379> BITFIELD name overflow fail incrby u2 6 1
1) (nil)
127.0.0.1:6379> get name
"Kava"
127.0.0.1:6379>

sat 表示停留在最大/ 最小值 (饱和截断):

127.0.0.1:6379> get name
"Kava"
127.0.0.1:6379> BITFIELD name overflow sat incrby u2 6 1
1) (integer) 3
127.0.0.1:6379> BITFIELD name overflow sat incrby u2 6 1
1) (integer) 3
127.0.0.1:6379> BITFIELD name overflow sat incrby u2 6 1
1) (integer) 3
127.0.0.1:6379> BITFIELD name overflow sat incrby u2 6 1
1) (integer) 3
127.0.0.1:6379>



作者:缘木与鱼
链接:https://www.jianshu.com/p/cbdf100f7ec0
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。