Redis 位图(bitMap)
位图(bitMap)
应用场景
在平时开发中, 可能有一些bool型数据需要存取,如用户一年的签到记录,签了是1, 没签是0, 要记录365天。如果使用普通的key/value, 每个用户要记录365个, 当用户上亿时候, 需要的存储空间是惊人的。
为了解决这个问题, Redis提供了位图数据结构, 这样每天的签到记录只占据一个位, 365天就是365个位, 46个字节就可以完全容纳下, 大大节约了存储空间。
位图不是特殊的数据结构, 它的内容其实就是普通的字符串, 也就是byte数组。可以使用普通的get/set直接获取和设置整个位图的内容, 也可以使用位图操作getbit/setbit等将byte数组看成[位数组]来处理。
基本使用
127.0.0.1:6379> setbit s 1 1
(integer) 0
127.0.0.1:6379> setbit s 2 1
(integer) 0
127.0.0.1:6379> setbit s 4 1
(integer) 0
127.0.0.1:6379> setbit s 9 1
(integer) 0
127.0.0.1:6379> setbit s 10 1
(integer) 0
127.0.0.1:6379> setbit s 13 1
(integer) 0
127.0.0.1:6379> setbit s 15 1
(integer) 0
127.0.0.1:6379> get s
"he"
# [零存零取]
127.0.0.1:6379> setbit w 1 1
(integer) 0
127.0.0.1:6379> setbit w 2 1
(integer) 0
127.0.0.1:6379> setbit w 4 1
(integer) 0
# 获取某个具体位置的值 0/1
127.0.0.1:6379> getbit w 1
(integer) 1
127.0.0.1:6379> getbit w 2
(integer) 1
127.0.0.1:6379> getbit w 4
(integer) 1
127.0.0.1:6379> getbit w 5
(integer) 0
# 整存零取
127.0.0.1:6379> set w h # 整存
(integer) 0
127.0.0.1:6379> getbit w 1
(integer) 1
127.0.0.1:6379> getbit w 2
(integer) 1
127.0.0.1:6379> getbit w 4
(integer) 1
127.0.0.1:6379> getbit w 5
(integer) 0
统计和查找
Redis提供了位图统计指令bitcount和位图查找指令bitpos, bitcount用来统计定位范围内1的个数, bitpos用来查找指定范围内出现第一个0或1。
比如我们可以通过bitcount统计用户一共签到了多少天, 通过bitpos指令查找用户从哪一天开始第一次签到。如果指定了范围参数[start, end], 就可以统计在某个时间范围内用户签到了多少天, 用户自某天以后的哪天开始签到的。
127.0.0.1:6379> set w hello
OK
127.0.0.1:6379> bitcount w
(integer) 21
# 第一个字符中 1 的位数
127.0.0.1:6379> bitcount w 0 0
(integer) 3
# 前两个字符中 1 的位数
127.0.0.1:6379> bitcount w 0 1
(integer) 7
# 第一个 0 位
127.0.0.1:6379> bitpos w 0
(integer) 0
# 第一个 1 位
127.0.0.1:6379> bitpos w 1
(integer) 1
# 从第二个字符算起,第一个 1 位
127.0.0.1:6379> bitpos w 1 1 1
(integer) 9
# 从第三个字符算起,第一个 1 位
127.0.0.1:6379> bitpos w 1 2 2
(integer) 17
魔术指令bitfield
之前设置(setbit)和获取(getbit)指定位的值都是单个单位的, 如果要一次操作多个位,就必须使用管道来处理。 不过Redis3.2版本后新增了一个功能强大的指令。bitfield有三个子指令, 分别是get/set/incrby,它们都可以对指定位片段进行读写, 但是最多只能处理64个连续的位, 如果超过64位, 就的使用多个子指令, bitfield可以一次只从多个子指令。
127.0.0.1:6379> set w hello
OK
# 从第一个位开始取 4 个位,结果是无符号数 (u)
127.0.0.1:6379> bitfield w get u4 0
(integer) 6
# 从第三个位开始取 3 个位,结果是无符号数 (u)
127.0.0.1:6379> bitfield w get u3 2
(integer) 5
# 从第一个位开始取 4 个位,结果是有符号数 (i)
127.0.0.1:6379> bitfield w get i4 0
1) (integer) 6
# 从第三个位开始取 3 个位,结果是有符号数 (i)
127.0.0.1:6379> bitfield w get i3 2
1) (integer) -3
# 一次执行多个子指令
127.0.0.1:6379> 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
# 从第 8 个位开始,将接下来的 8 个位用无符号数 97 替换
127.0.0.1:6379> bitfield w set u8 8 97
1) (integer) 101
127.0.0.1:6379> get w "hallo"
# incrby指令
127.0.0.1:6379> set w hello
OK
# 从第三个位开始,对接下来的 4 位无符号数 +1
127.0.0.1:6379> bitfield w incrby u4 2 1
1) (integer) 11
127.0.0.1:6379> bitfield w incrby u4 2 1
1) (integer) 12
127.0.0.1:6379> bitfield w incrby u4 2 1
1) (integer) 13
127.0.0.1:6379> bitfield w incrby u4 2 1
1) (integer) 14
127.0.0.1:6379> bitfield w incrby u4 2 1
1) (integer) 15
# 溢出折返了
127.0.0.1:6379> bitfield w incrby u4 2 1
1) (integer) 0
#饱和截断 SAT
127.0.0.1:6379> set w hello
OK
127.0.0.1:6379> bitfield w overflow sat incrby u4 2 1 1) (integer) 11
127.0.0.1:6379> bitfield w overflow sat incrby u4 2 1 1) (integer) 12
127.0.0.1:6379> bitfield w overflow sat incrby u4 2 1 1) (integer) 13
127.0.0.1:6379> bitfield w overflow sat incrby u4 2 1 1) (intege) 14
127.0.0.1:6379> bitfield w overflow sat incrby u4 2 1 1) (integer) 15
# 保持最大值
127.0.0.1:6379> bitfield w overflow sat incrby u4 2 1 1) (integer) 15
#失败不执行 FAIL
127.0.0.1:6379> set w hello
OK
127.0.0.1:6379> bitfield w overflow fail incrby u4 2 1 1) (integer) 11
127.0.0.1:6379> bitfield w overflow fail incrby u4 2 1 1) (integer) 12
127.0.0.1:6379> bitfield w overflow fail incrby u4 2 1 1) (integer) 13
127.0.0.1:6379> bitfield w overflow fail incrby u4 2 1 1) (integer) 14
127.0.0.1:6379> bitfield w overflow fail incrby u4 2 1 1) (integer) 15
# 不执行
127.0.0.1:6379> bitfield w overflow fail incrby u4 2 1
1) (nil)