这节我们学习位运算,算是redis我记不住的那些命令的完结,后续会更新其他内容。
字符串: 从 frank到grank的转换, f 的ascii码为“0110 0110”,g的ascii码为“0110 0111”,从左到右,从0开始,f和g的差异在于最后的1,所以我们使用setbit name 7 1 ,那我们想grank转换为grcnk呢??? 也就是a变成c
127.0.0.1:6379> set name frank OK 127.0.0.1:6379> get name frank 127.0.0.1:6379> setbit name 7 1 0 127.0.0.1:6379> getbit name 7 1 127.0.0.1:6379> get name grank 127.0.0.1:6379> setbit name 22 1 0 127.0.0.1:6379> get name grcnk
这22是怎么算出来的呢? 22 = 2*8+6 a的ascii码为"0110 0001" , c的ascii码为“0110 0011”
gr为2*8是16位,而a和c的差异在第6位,所以得出22
二维的例子:
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
我们有一个10*10的矩阵,水平为x坐标,垂直为y坐标,即(x,y)则上述图中1的坐标为 (4,0)、 (4,1)、(4,2) 、(5,2)、 (5,3)、(5,4)、(5,5)、(6,5) ,而在一个binary string中的偏移量offset是需要进行转换:
offset = y * max_width + x ,这里的max_width为10
coordinate坐标 | offset偏移量 |
(4,0) | 4 |
(4,1) | 14 |
(4,2) | 24 |
(5,2) | 25 |
(5,3) | 35 |
(5,4) | 45 |
(5,5) | 55 |
(6,5) | 56 |
我们使用这个二位的矩阵做什么呢? 比如有一个游戏,在二维平面上通过上下左右键进行行走,我们需要记录玩家走的路径和走的步数,那我们如何做呢? 我们可以使用redis的bitwise来进行操作,假设玩家id为2 ,在第3关卡的第4个地图,那么 我们记录这个key为
player:2:episode:3:map:4
这个key标识了某个玩家在某关卡的某个地图的详细信息。
每当玩家走了一步,我们用setbit key offset 1,而bitcount来计算这个玩家走了多少步。
每次从redis中取出玩家的信息,就推算出他的路径和步数。
0. setbit : 字符串是从左到右,从0开始,二维坐标则是从左到右,从下向上,从0开始
1. getbit : 获取某一个偏移量的值
2. bitcount : 获取二进制字符串的1的个数
3. bitop : 对一个或多个二进制位串执行包括 并and,或or,异或xor,非not,并将结果计算得出保存在dest-key键中
4. bitpos : 获取第一个 1或0 的偏移量,redis version 7.0版本,格式: bitpos key bit start end byte|bit , 7.0版本以下为: bitpos key bit start end
返回值: 当有一个全0的字符串,寻找1则返回 -1
当有一个全1的字符串,寻找0则返回字符串长度,为什么返回字符串长度? 比如全1字符串的长度为9,索引为0-8,所以从左到右算,0的索引是9,这也是为什么是字符串长度。
从根本上说,如果你寻找0且没有指定范围或者只有start参数,那么会考虑在字符串的右侧添加0.
然而,如果你寻找0且指定了范围如start和end。如果在指定的范围内没有找到0,则返回-1
5. bitfield : 这个命令其实就是按照你的需求来分配的,可将多个字段存储在一个key中,即一个bitmap中,提升了内存的使用效率,特别是可用于实时的分析。例如你需要一个带符号数5位,位置在第100位到104位, i5 代表是5位有符号整型数字,100代表offset即偏移量(起始位置),15为你所设定的值,15的二进制值为(01111),注意这是补码的值,一定注意。可参考原码,反码和补码(复习)
计算机没有减法都是加法,补码出现不仅仅修复了0的符号以及存在两个编码的问题, 而且还能够多表示一个最低数,即 5位带符号数(-16,+15)
第二种offset定位的方式是:使用“#”,从0开始,从左到右,这样的好处是不用手动的计算位数并且两个变量是紧紧挨着的。
还有一个是对溢出的控制,分为三种控制溢出的方式即wrap,sat,fail,默认为wrap
wrap是循环,即到了最大值再加1,则为最小值,依次循环。
sat是固定,即到了最大值则不再变化
fail是失败,即到了最大值再加1则此操作失败。
格式为: bitfield mykey overflow wrap incrby u2 #0 1
bitfield最佳实践: 时序记录数据 ,文中是记录一天之内每分钟的温度,通过8位无符号数字,通过offset “#”进行定位,一分钟的温度所占用一个字节,一天总共占用1.44KB的大小
127.0.0.1:6379> BITFIELD bit-ts SET u8 #59 23 SET u8 #719 25
127.0.0.1:6379> bitfield mykey set i5 100 15 0 127.0.0.1:6379> getbit mykey 100 0 127.0.0.1:6379> getbit mykey 101 1 127.0.0.1:6379> getbit mykey 102 1 127.0.0.1:6379> getbit mykey 103 1 127.0.0.1:6379> getbit mykey 104 1 # 当对带符号的值 增加1,会出现溢出的情况,即 -16 # 但二进制位数显示是一致的,也就是说 10000 这是-16的补码,所以这里的值才为-16 127.0.0.1:6379> bitfield mykey incrby i5 100 1 -16 127.0.0.1:6379> getbit mykey 100 1 127.0.0.1:6379> getbit mykey 101 0 127.0.0.1:6379> getbit mykey 102 0 127.0.0.1:6379> getbit mykey 103 0 127.0.0.1:6379> getbit mykey 104 0 127.0.0.1:6379> bitfield mykey2 set i5 100 -16 0 127.0.0.1:6379> getbit mykey2 100 1 127.0.0.1:6379> getbit mykey2 101 0 127.0.0.1:6379> getbit mykey2 102 0 127.0.0.1:6379> getbit mykey2 103 0 127.0.0.1:6379> getbit mykey2 104 0 127.0.0.1:6379> bitfield mystring set i8 #0 100 set i8 #1 200 0 0 127.0.0.1:6379> getbit mystring 0 0 127.0.0.1:6379> getbit mystring 1 1 127.0.0.1:6379> getbit mystring 2 1 127.0.0.1:6379> getbit mystring 3 0 127.0.0.1:6379> getbit mystring 4 0 127.0.0.1:6379> getbit mystring 5 1 127.0.0.1:6379> getbit mystring 6 0 127.0.0.1:6379> getbit mystring 7 0 127.0.0.1:6379> 127.0.0.1:6379> getbit mystring 8 1 127.0.0.1:6379> getbit mystring 9 1 127.0.0.1:6379> getbit mystring 10 0 127.0.0.1:6379> getbit mystring 11 0 127.0.0.1:6379> getbit mystring 12 1 127.0.0.1:6379> getbit mystring 13 0 127.0.0.1:6379> getbit mystring 14 0 127.0.0.1:6379> getbit mystring 15 0 127.0.0.1:6379> bitfield mystring get i8 #1 -56 ()是补码,它的值为-56,而不是200,这是因为溢出了
6. getrange : 获取区间内的值
7. strlen : 获取字符串的长度,单位为字节
完整示例:
# 0. 127.0.0.1:6379> setbit mybinarystring 4 1 0 127.0.0.1:6379> setbit mybinarystring 14 1 0 127.0.0.1:6379> setbit mybinarystring 24 1 0 127.0.0.1:6379> setbit mybinarystring 25 1 0 127.0.0.1:6379> setbit mybinarystring 35 1 0 127.0.0.1:6379> setbit mybinarystring 45 1 0 127.0.0.1:6379> setbit mybinarystring 55 1 0 127.0.0.1:6379> setbit mybinarystring 56 1 0 # 1. 127.0.0.1:6379> getbit mybinarystring 24 1 # 2. 127.0.0.1:6379> bitcount mybinarystring 8 # 3. 我们直接copy mybinarystring的值 127.0.0.1:6379> copy mybinarystring mybinarystring2 1 127.0.0.1:6379> bitop and result mybinarystring mybinarystring2 8 127.0.0.1:6379> getbit result 4 1 127.0.0.1:6379> bitop or result2 mybinarystring mybinarystring2 8 127.0.0.1:6379> getbit result2 4 1 127.0.0.1:6379> bitop xor result3 mybinarystring mybinarystring2 8 127.0.0.1:6379> getbit result3 55 0 127.0.0.1:6379> getbit result3 56 0 127.0.0.1:6379> bitop not result4 mybinarystring 8 127.0.0.1:6379> getbit result4 45 0 127.0.0.1:6379> getbit result4 44 1 # 4. 127.0.0.1:6379> bitpos result4 1 0 127.0.0.1:6379> set mykey "\xff\xf0\x00" OK 127.0.0.1:6379> get mykey ▒▒ 127.0.0.1:6379> bitpos mykey 0 12 127.0.0.1:6379> set mykey "\x00\xff\xf0" OK 127.0.0.1:6379> bitpos mykey 1 0 8 127.0.0.1:6379> bitpos mykey 1 2 16 127.0.0.1:6379> bitpos mykey 1 3 -1 127.0.0.1:6379> bitpos mykey 1 1 8 127.0.0.1:6379> bitpos mykey 1 0 -1 8 127.0.0.1:6379> bitpos mykey 1 0 1 8 127.0.0.1:6379> bitpos mykey 1 0 0 -1 # 我的redis的版本是6.0,所以会出现语法错误 127.0.0.1:6379> bitpos mykey 1 0 1 byte ERR syntax error 127.0.0.1:6379> bitpos mykey 1 0 1 bit ERR syntax error # 5. bitfield # 有一个在线游戏,需要记录每个玩家的金币数和杀敌数,这个游戏是让人痴迷的,长度为32位,使用 bitfield 可以存储这两个关键信息。 # 游戏初始化为每个玩家为1000金币 127.0.0.1:6379> BITFIELD player:1:stats SET u32 #0 1000 1) (integer) 0 # 当杀掉妖怪救出人质后将会增加50金币,并增加一个妖怪数量 127.0.0.1:6379> BITFIELD player:1:stats INCRBY u32 #0 50 INCRBY u32 #1 1 1) (integer) 1050 2) (integer) 1 # 花费999金币买一个装备 127.0.0.1:6379> BITFIELD player:1:stats INCRBY u32 #0 -999 1) (integer) 51 # 获取玩家的信息 127.0.0.1:6379> BITFIELD player:1:stats GET u32 #0 GET u32 #1 1) (integer) 51 2) (integer) 1 # overflow # sat 127.0.0.1:6379> bitfield mykey overflow sat incrby u2 300 1 1) (integer) 1 127.0.0.1:6379> bitfield mykey overflow sat incrby u2 300 1 1) (integer) 2 127.0.0.1:6379> bitfield mykey overflow sat incrby u2 300 1 1) (integer) 3 127.0.0.1:6379> bitfield mykey overflow sat incrby u2 300 1 1) (integer) 3 127.0.0.1:6379> bitfield mykey overflow sat incrby u2 300 1 1) (integer) 3 # wrap,这是默认值 127.0.0.1:6379> bitfield mykey overflow wrap incrby u2 400 1 1) (integer) 1 127.0.0.1:6379> 127.0.0.1:6379> bitfield mykey overflow wrap incrby u2 400 1 1) (integer) 2 127.0.0.1:6379> bitfield mykey overflow wrap incrby u2 400 1 1) (integer) 3 127.0.0.1:6379> bitfield mykey overflow wrap incrby u2 400 1 1) (integer) 0 127.0.0.1:6379> bitfield mykey overflow wrap incrby u2 400 1 1) (integer) 1 # 6. 获取这个key的区间的数字 127.0.0.1:6379> getrange mystring 0 15 "d\xc8" d 代表的ascii码,即 0110 0100 \xc8 代表十六进制,即 1100 1000 # 7. 获取这个key的长度,单位为字节 127.0.0.1:6379> strlen mystring 2