浅谈Redis位图(Bitmap)及Redis二进制中的问题


Posted in Redis onJuly 15, 2021

Redis位图(Bitmap)及二进制的问题

SETBIT key offset value

对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。位的设置或清除取决于 value 参数,可以是 0 也可以是 1 。当 key 不存在时,自动生成一个新的字符串值。字符串会进行伸展(grown)以确保它可以将 value 保存在指定的偏移量上。当字符串值进行伸展时,空白位置以 0 填充。offset 参数必须大于或等于 0 ,小于 2^32 (bit 映射被限制在 512 MB 之内)。返回值是指定偏移量原来存储的位

对使用大的 offset 的 SETBIT 操作来说,内存分配可能造成 Redis 服务器被阻塞。具体参考 SETRANGE 命令,warning(警告)部分。

127.0.0.1:6379> setbit bit 3 1
(integer) 0
127.0.0.1:6379> getbit bit 0
(integer) 0
127.0.0.1:6379> getbit bit 1
(integer) 0
127.0.0.1:6379> getbit bit 2
(integer) 0
127.0.0.1:6379> getbit bit 3
(integer) 1127.0.0.1:6379> setbit bit 3 0(integer) 1

GETBIT key offset

返回key对应的string在offset处的bit值,当offset超出了字符串长度的时候,这个字符串就被假定为由0比特填充的连续空间。当key不存在的时候,它就认为是一个空字符串,所以offset总是超出范围,然后value也被认为是由0比特填充的连续空间。到内存分配。

127.0.0.1:6379> getbit yhq 5
(integer) 0
127.0.0.1:6379> getbit bit 10
(integer) 0
127.0.0.1:6379> getbit bit 3
(integer) 1

BITCOUNT key [start] [end]

计算给定字符串中,被设置为 1 的比特位的数量。一般情况下,给定的整个字符串都会被进行计数,通过指定额外的 start 或 end 参数,可以让计数只在特定的位上进行*start和end的单元是字节而不是bit*。start 和 end 参数的设置和 GETRANGE 命令类似,都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位,以此类推。不存在的 key 被当成是空字符串来处理,因此对一个不存在的 key 进行 BITCOUNT 操作,结果为 0 。

127.0.0.1:6379> set mykey foobar
OK
127.0.0.1:6379> bitcount youkey
(integer) 0
127.0.0.1:6379> bitcount mykey
(integer) 26
127.0.0.1:6379> bitcount mykey 0 0   # "f" 0110 0110
(integer) 4
127.0.0.1:6379> bitcount mykey 1 1   # "o" 0110 1111
(integer) 6

BITOP operation destkey key [key ...]

对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。

operation 可以是 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种:

  • 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 。

除了 NOT 操作之外,其他操作都可以接受一个或多个 key 作为输入。

处理不同长度的字符串,当 BITOP 处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作 0 。空的 key 也被看作是包含 0 的字符串序列

127.0.0.1:6379> setbit bit1 0 1
(integer) 0
127.0.0.1:6379> setbit bit1 3 1
(integer) 0
127.0.0.1:6379> setbit bit2 0 1
(integer) 0
127.0.0.1:6379> setbit bit2 1 1
(integer) 0
127.0.0.1:6379> setbit bit2 3 1
(integer) 0
127.0.0.1:6379> bitop and andbit bit1 bit2
(integer) 1
127.0.0.1:6379> getbit andbit 0
(integer) 1
127.0.0.1:6379> getbit andbit 1
(integer) 0
127.0.0.1:6379> getbit andbit 2
(integer) 0
127.0.0.1:6379> getbit andbit 3
(integer) 1

BITPOS key bit [start] [end]

返回字符串里面第一个被设置为1或者0的bit位。如果我们在空字符串或者0字节的字符串里面查找bit为1的内容,那么结果将返回-1。

如果我们在字符串里面查找bit为0而且字符串只包含1的值时,将返回字符串最右边的第一个空位。如果有一个字符串是三个字节的值为0xff的字符串,那么命令BITPOS key 0将会返回24,因为0-23位都是1。基本上,我们可以把字符串看成右边有无数个0。然而,如果你用指定start和end范围进行查找指定值时,如果该范围内没有对应值,结果将返回-1。

127.0.0.1:6379> getbit num 0
(integer) 0
127.0.0.1:6379> getbit num 1
(integer) 0
127.0.0.1:6379> getbit num 2
(integer) 1
127.0.0.1:6379> getbit num 3
(integer) 1
127.0.0.1:6379> getbit num 4
(integer) 0
127.0.0.1:6379> getbit num 5
(integer) 0
127.0.0.1:6379> getbit num 6
(integer) 1
127.0.0.1:6379> getbit num 7
(integer) 0
127.0.0.1:6379> getbit num 8
(integer) 0
127.0.0.1:6379> bitpos num 1
(integer) 2
127.0.0.1:6379> bitpos yhqqhh 1
(integer) -1
127.0.0.1:6379> bitpos yhqqhh 0
(integer) 0

Redis二进制中的问题1 : 数字全部是char类型表示

127.0.0.1:6379> set num 2
OK
127.0.0.1:6379> bitcount num
(integer) 3

Redis中,数字类型其实是以ASCII形式展现的,即 2=>50,正常2的(一个字节8个二进制位)表示为 00000010,bitcount为1。而Redis数字是字符的"2",所以 "2"[50] 的表示为 00110010,bitcount为3。

Redis中二进制从左到右(正常从右到左)

"2"的二进制为 00110010

127.0.0.1:6379> getbit num 0
(integer) 0
127.0.0.1:6379> getbit num 1
(integer) 0
127.0.0.1:6379> getbit num 2
(integer) 1
127.0.0.1:6379> getbit num 3
(integer) 1
127.0.0.1:6379> getbit num 4
(integer) 0
127.0.0.1:6379> getbit num 5
(integer) 0
127.0.0.1:6379> getbit num 6
(integer) 1
127.0.0.1:6379> getbit num 7
(integer) 0

redis高级数据结构---bitmap

场景引入

我们在正常开发环境中,有时候需要将bool型数据进行存取,比如用户一年里面签到了多少次,签到了设置1,没签到设置0,要记录365天,如果使用普通的key/value形式存储,每个用户就需要占据365键值对,当用户量上亿的时候,需要惊人的存储空间。更何况是一年的。 为了解决这种问题,redis提出了bitmap的数据结构,这样每天用户签到只需要占据一个位,365天就是365位,46个字节,一个稍微长一点的字符串就可以完全容纳下一个用户一年的签到记录,大量的节省存储空间。位图的最小单位是比特(bit),每个bit的取值只能是0或1。

实现原理

位图不是特殊的数据结构,他的内容实际就是普通的字符串,也就是byte数组,我们可以使用普通的get/set直接获取和设置整个位图的内容,也可以使用位图操作getbit/setbit等将byte数组看成位数组来处理。

基本用法

redis的位数组是自动扩展的,如果设置了某个偏移位置超出了现有的内容范围,就会自动将位数组进行零扩充。

举例:

“h”的ASCII码值是:01101000

"e"的ASCII码值是: 01100101

"l"的ASCII码值是:0110 1100

"o"的ASCII码值是:0110 1111

将“he” 连起来是:0110100001100101

即1,2,4,9,10,13,15位为1

浅谈Redis位图(Bitmap)及Redis二进制中的问题

以上的示范可以称之为“零存整取”,即使用单个位操作设置位值,使用单个位操作获取具体位值。

还有另一种操作称之为“整存零取”,即使用字符串操作批量设置值,使用单个位操作获取具体位值。

以上介绍了setbit,getbit的操作,redis还提供了位图的统计和查找指令:bitcount,bitpos

bitcount同来统计指定位值范围内1的个数。

bitpos用来查找指定范围内出现的第一个0或者1。

127.0.0.1:6379> set w hello
OK
127.0.0.1:6379> bitcount w    //统计所有的1的个数
(integer) 21
127.0.0.1:6379> bitcount w 0 0  //统计第一个字符中1的个数
(integer) 3
127.0.0.1:6379> bitcount w 0 1  //统计前两个字符中1的个数
(integer) 7
127.0.0.1:6379> bitpos w 0   //第一个0位
(integer) 0
127.0.0.1:6379> bitpos w 1  //第一个1位
(integer) 1
127.0.0.1:6379> bitpos w 1 1 1   // 从第二个字符算起,第一个1位
(integer) 9
127.0.0.1:6379> bitpos w 1 2 2   //  从第三个字符算起,第一个1位
(integer) 17
127.0.0.1:6379>

接下来介绍魔术指令 bitfield:

主要解决setbit/getbit只能操作单个位的弊端。redis 3.2+新增功能。

bitfield有三个子指令:get、set、incrby,他们都可以对指定位片段进行读写,但是最多只能处理64个连续的位,如果超过64位,就得使用多个子指令,bitfield可以一次执行多个子指令。

127.0.0.1:6379> set w hello
OK
127.0.0.1:6379> bitfield w get u4 0  //从第一个位开始取4个位,结果是无符号数(u)
1) (integer) 6
127.0.0.1:6379> bitfield w get u3 2  //从第三个位开始取3个位,结果是无符号数
1) (integer) 5
127.0.0.1:6379> bitfield w get i4 0  //从第一个位开始取4个位,结果是有符号数  (i)
1) (integer) 6
127.0.0.1:6379> bitfield w get i3 2  //从第三个位开始取3个位,结果是有符号数
1) (integer) -3
127.0.0.1:6379>

所谓有符号数是指获取的位数组中第一个位是符号位,剩下的才是值,如果第一个位是1,那就是负数。

无符号数表示非负数,没有符号位,获取的位数全部是是值。

有符号数最多可以获取64位,无符号数只能获取63位。如果超出限制,redis会报参数错误。

接下来演示一个多指令:

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
127.0.0.1:6379>

接下来使用set子指令将第二个字符e,改成a,a的ASCII值是97

127.0.0.1:6379> bitfield w set u8 8 97
1) (integer) 101
127.0.0.1:6379> get w
"hallo"
127.0.0.1:6379>

接下来介绍第三个子指令incrby,他用来对指定范围的位进行自增操作,既然是自增操作,就会存在溢出的情况,如果增加了正数,会出现向上溢出,如果是增加了负数,就会出现向下溢出。redis的默认处理方式是折返操作,如果出现了溢出,就将溢出的符号位丢掉。如果是8位无符号数255,加1后就会溢出,会全部变为0.如果是8位有符号数127,加1后就会溢出变成-128。

127.0.0.1:6379> set w hello
OK
127.0.0.1:6379> bitfield w incrby u4 2 1   //从第三个位开始,对接下来的4位无符号数进行自增+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
127.0.0.1:6379>

bitfield指令提供了溢出策略子指令overflow,用户可以选择溢出行为,默认是折返(wrap),还可以选择失败(fail)------报错不执行,以及饱和截断(sat)-----超过了范围就停留在最大值或者最小值。overflow指令只影响接下来的第一条指令,这条指令执行完后溢出策略会变成默认值折返。

饱和截断:

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) (integer) 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
127.0.0.1:6379>

失败不执行:

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)
127.0.0.1:6379>

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Redis 相关文章推荐
Redis如何一键部署脚本
Apr 12 Redis
Redis 配置文件重要属性的具体使用
May 20 Redis
在redisCluster中模糊获取key方式
Jul 09 Redis
springboot使用Redis作缓存使用入门教程
Jul 25 Redis
Redisson实现Redis分布式锁的几种方式
Aug 07 Redis
Redis安装使用RedisJSON模块的方法
Mar 23 Redis
一文搞懂Redis中String数据类型
Apr 03 Redis
Redis 报错 error:NOAUTH Authentication required
May 15 Redis
Redis特殊数据类型bitmap位图
Jun 01 Redis
Redis入门基础常用操作命令整理
Jun 01 Redis
Redis实现主从复制方式(Master&Slave)
Jun 21 Redis
Redis+AOP+自定义注解实现限流
Jun 28 Redis
Redis做数据持久化的解决方案及底层原理
Jul 15 #Redis
Redis Cluster集群动态扩容的实现
redis requires ruby version2.2.2的解决方案
Jul 15 #Redis
厉害!这是Redis可视化工具最全的横向评测
Redis性能监控的实现
Redis 彻底禁用RDB持久化操作
Jul 09 #Redis
在redisCluster中模糊获取key方式
You might like
十大催泪虐心动漫,你能坚持看到第几部?
2020/03/04 日漫
php表单提交问题的解决方法
2011/04/12 PHP
thinkphp备份数据库的方法分享
2015/01/04 PHP
php项目中类的自动加载实例讲解
2019/09/12 PHP
JS无限树状列表实现代码
2011/01/11 Javascript
js过滤特殊字符输入适合输入、粘贴、拖拽多种情况
2014/03/22 Javascript
javascript实例分享---具有立体效果的图片特效
2014/06/08 Javascript
jQuery提示插件alertify使用指南
2015/04/21 Javascript
很全面的JavaScript常用功能汇总集合
2016/01/22 Javascript
Jquery on方法绑定事件后执行多次的解决方法
2016/06/02 Javascript
js 获取当前web应用的上下文路径实现方法
2016/08/19 Javascript
Angular.js中定时器循环的3种方法总结
2017/04/27 Javascript
JS实现无缝循环marquee滚动效果
2017/05/22 Javascript
JS 中LocalStorage和SessionStorage的使用
2017/08/17 Javascript
Vue面试题及Vue知识点整理
2018/10/07 Javascript
详解vuex之store源码简单解析
2019/06/13 Javascript
VUE中使用HTTP库Axios方法详解
2020/02/05 Javascript
基于JavaScript实现表格隔行换色
2020/05/08 Javascript
nuxt.js服务端渲染中axios和proxy代理的配置操作
2020/11/06 Javascript
如何利用nodejs实现命令行游戏
2020/11/24 NodeJs
Python2实现的LED大数字显示效果示例
2017/09/04 Python
浅谈dataframe中更改列属性的方法
2018/07/10 Python
python 利用pywifi模块实现连接网络破解wifi密码实时监控网络
2019/09/16 Python
Python中断多重循环的思路总结
2019/10/04 Python
使用python去除图片白色像素的实例
2019/12/12 Python
pytorch程序异常后删除占用的显存操作
2020/01/13 Python
Django Serializer HiddenField隐藏字段实例
2020/03/31 Python
Python绘制全球疫情变化地图的实例代码
2020/04/20 Python
详解Python Celery和RabbitMQ实战教程
2021/01/20 Python
Clarins娇韵诗英国官网:来自法国的天然护肤品牌
2017/04/18 全球购物
新员工试用期自我鉴定
2014/04/17 职场文书
宾馆前台接待岗位职责
2015/04/02 职场文书
小学教师工作总结2015
2015/04/07 职场文书
晶体管来复再生式二管收音机
2021/04/22 无线电
ConditionalOnProperty配置swagger不生效问题及解决
2022/06/14 Java/Android
Nginx跨域问题解析与解决
2022/08/05 Servers