Redis学习之旅 位图(BitMap)
Redis学习之旅 位图(BitMap)
单独给位图开一章,是因为这块的东西确实玩法众多,面试也常常遇到,干脆将其拆分为独立的一章
Redis BIT相关命令
BITCOUNT
从字面意思上来看,bitcount翻译过来叫位统计,那么统计的是什么呢?,其实是01串中1的个数
- OK,这不简单么,我知道了,字符串1 BITCOUNT的结果肯定是1,因为1的二进制是 0000 0001嘛,我们看下结果
我了个大擦,怎么会是3?,这就又回到我们在String篇里提到的一个概念,Redis String类型的数据,都是按一个字节一个字节来保存的,Redis实际上处理的是一个个的字节,每个字节又如何去表示呢?不管编码字符集是什么,它有一个是不变的,就是ASCII码,其他已知的字符集(UTF-8,GBK)都是在ASCII码的基础上进行的扩展
ASCII 码表
通过对照表可以看到,字符1在ASCII里的表示 ,实际上赞同于十进制的49,转成二进制是 00110001,数一下,确实是3个1
- NICE!那我就明白了,后面的start 和 end 明显就是二进制位的偏移么,如果我把start定为3 ,end 定为7,一定会返回2,因为从第四位到第八位,里面有两个1
-惊不惊喜?意不意外?是的,虽然bitcount是位级别的统计,但是它的参数并不是位级别的,而是字节级别的!
从上面可以得出几个结论
- bitcount是可以越界的,直接返回0
- 针对不存在的key,直接返回0
- 它的start,end参数在使用与含义上等于SETRANGE、GETRANGE,是字节级别的偏移
GETBIT
对 key 所储存的字符串值,获取指定偏移量上的位,怎么理解呢?看下面的例子。还是以1为例 ,我们已经知道,它对应的ascii位置为49,对应的二进制是00110001
从执行的结果可以看出
- 位获取是从高位向低位进行计算的
- 获取到的是指定偏移后的位上的0或者1
- 越界直接返回0
有人可能会想,那么多个字符咋办?演示一下
推算一下,13= 8+5,即求的是第二个字符的第6位,我们看下b的二进制 01100010,第6位为0,第7位为1
接着提问题,中文呢?提到中文就不得不说下编码,UTF-8对中文的编码占三个字节,GBK编码占两个字节,以UTF-8为例进行测试
可以看到,确实是一个汉字占据三个字节,每个字节内填充的是一个16进制数,直接取第3个字节,即\xad,由16进制转换为2进制为10101101,看下结果完全匹配
SETBIT
这块和GETBIT完全一致,就是把对应的位上修改为0或者1,简单演示一下
1.设置的位的有效值只有0/1,其他值会抛出异常
BITPOS
即Bit Position,查找指定位的准确位置,啥意思呢?就是在给定的字符串中,按二进制,找到第一个你需要查找的0或者1,并返回它的位索引位置,\xe4 对应二进制 11100100
- 不给出偏移时,是从第一个字符开始计算,找到一个匹配的就返回
- 无法按位给出偏移量,只能按字节来给范围
BITOP
字面意思字节操作,实际指可以将多个字符串进行位运算,并将结果存放到另一个指定Key上
支持如下四种操作
- BITOP AND destkey key [key …] ,对一个或多个key求位与,并将结果保存到destkey
- BITOP OR destkey key [key …] ,对一个或多个key求位或,并将结果保存到destkey
- BITOP XOR destkey key [key …] ,对一个或多个key求位异或,并将结果保存到destkey
- BITOP NOT destkey key ,对给定key求位非,并将结果保存到destkey
BITOP AND
- 按位与,a 对应的ascii二进制 01100001 ,b对应的ascii二进制为01100010
01100001 & 01100010 = 01100000,即字符 `
BITOP OR
- 01100001 | 01100010 = 01100011,即字符 c
BITOP XOR
- 01100001 ^ 01100010 = 00000011,即字符 ●
BITOP NOT
需要注意的是,位非操作只可以有一个key参与运算
- ~01100001 = 10011110
BITFIELD
BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]
按Redis提示,它是在string中取某些位做int类型的运算
可以一次对多个位进行位操作.三个子指令get,set,incrby都可以读写指定位的片段,但一次最多只能处理64个连续的位,如果超过64位,则需要使用多条命令实现,只能取64位是因为Redis对int类型的数据定义就是64位,如果算上符号位,可用位只有63位
先算一下对应的位,防止后面懵圈
字符 | ASCII码序号(十进制) | 对应的二进制 |
---|---|---|
h | 104 | 01101000 |
e | 101 | 01100101 |
l | 108 | 01101100 |
o | 111 | 01101111 |
, | 44 | 00101100 |
w | 119 | 01110111 |
r | 114 | 01110010 |
d | 100 | 01100100 |
! | 33 | 00100001 |
BITFIELD GET
bitfield k1 get u4 17 :偏移17位,取出4位二进制码转换成无符号十进制
在K1的这个Value的字符串"hello,world!"中,获取第18位起,连续的3个字节,即字符“L”的第2位开始,向后取连续的4个位,即 0<开始>1101<结束>100,然后按无符号计算,1101转十进制,等于13
bitfield k1 get i4 17:偏移17位,取出4位二进制码转成带符号十进制
同样取出1101,这是一个负数
原码1101,反码 0010,补码0011,0011即为3,计算结果应该为-3
BITFIELD SET
set和get的思路就是反着来,把给定的位使用你给定的十进制数进行填充
BITFIELD k1 set u4 2 13 ,就是将13转为无符号二进制1101,从第3个位开始,连续替换4位,原来的h是01101000,替换完后成了01110100,二进制对应116,即t
- 如果转出的数字(1101)小于要覆盖的范围(14),高位补0
- 如果转出的数字(100110)大于要覆盖的范围(4),计算的结果为00011000,明显,这是把高位,连带符号位一起扔掉了
![]()
BITFIELD INCRBY
前面都是对位的直接覆盖处理,这里就开始玩计算了,计算就会有溢出,Redis提供了三种策略
- WRAP: 回环算法(默认),适用于有符号和无符号整型两种类型。对于无符号整型,回环计数将对整型最大值进行取模操作(C语言的标准行为)。对于有符号整型,上溢从最负的负数开始取数,下溢则从最大的正数开始取数,例如,如果i8整型的值设为127,自加1后的值变为-128。
- SAT: 饱和算法,下溢之后设为最小的整型值,上溢之后设为最大的整数值。例如,i8整型的值从120开始加10后,结果是127,继续增加,结果还是保持为127。下溢也是同理,但量结果值将会保持在最负的负数值。
- FAIL: 失败算法,这种模式下,在检测到上溢或下溢时,不做任何操作。相应的返回值会设为NULL,并返回给调用者。
一定要注意要把overflow配置到incrby前面 ,否则不报错,不生效,使用默认wrap算法
位图运用
登录统计
一、统计某个时间段内的日活情况
解决思路,将用户编号,每个用户固定占用某个编号,不随用户的变更进行编号调整
按天开辟可以容纳用户的位行,举例,现在有100个用户,16个字节 16*8=128,就可以开辟出128个位0,将用户从0编号到99,以天创建key,用户登录后,与用户编号对应位置进行与1操作
- 准备三天的位
- 假定有四个用户,ID号分别是 12、35、54、83
日期 | 12 | 35 | 54 | 83 |
---|---|---|---|---|
20200526 | 在线 | 不在线 | 不在线 | 在线 |
20200527 | 不在线 | 在线 | 在线 | 在线 |
20200528 | 在线 | 在线 | 在线 | 不在线 |
- 将每天在线的用户标志位打上1
- 查看27号的日活
- 查看这三天一共有多少人没登录
100-4=96人没有登录