浅谈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 14 Redis
Redis IP地址的绑定的实现
May 08 Redis
redis内存空间效率问题的深入探究
May 17 Redis
浅谈Redis主从复制以及主从复制原理
May 29 Redis
在redisCluster中模糊获取key方式
Jul 09 Redis
使用redis实现延迟通知功能(Redis过期键通知)
Sep 04 Redis
使用RedisTemplat实现简单的分布式锁
Nov 20 Redis
聊聊redis-dump工具安装问题
Jan 18 Redis
使用Redis实现点赞取消点赞的详细代码
Mar 20 Redis
浅谈Redis的事件驱动模型
May 30 Redis
Redis基本数据类型哈希Hash常用操作命令
Jun 01 Redis
Redis主从复制操作和配置详情
Sep 23 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
强烈推荐:php.ini中文版(1)
2006/10/09 PHP
php对大文件进行读取操作的实现代码
2013/01/23 PHP
ThinkPHP提交表单时默认自动转义的解决方法
2014/11/25 PHP
CSS和Javascript简单复习资料
2010/06/29 Javascript
js 有框架页面跳转(target)三种情况下的应用
2013/04/09 Javascript
jquery弹出框的用法示例(2)
2013/08/26 Javascript
js获取系统的根路径实现介绍
2013/09/08 Javascript
js时间比较示例分享(日期比较)
2014/03/05 Javascript
node.js中的path.dirname方法使用说明
2014/12/09 Javascript
老生常谈js动态添加事件--- 事件委托
2016/07/19 Javascript
JS模拟实现方法重载示例
2016/08/03 Javascript
jquery.flot.js简单绘制折线图用法示例
2017/03/13 Javascript
vue如何从接口请求数据
2017/06/22 Javascript
关于TypeScript中import JSON的正确姿势详解
2017/07/25 Javascript
Node.js利用断言模块assert进行单元测试的方法
2017/09/28 Javascript
浅谈在Vue-cli里基于axios封装复用请求
2017/11/06 Javascript
vue项目中的webpack-dev-sever配置方法
2017/12/14 Javascript
JavaScript获取用户所在城市及地理位置
2018/04/21 Javascript
让你5分钟掌握9个JavaScript小技巧
2018/06/09 Javascript
浅谈react性能优化的方法
2018/09/05 Javascript
node和vue实现商城用户地址模块
2018/12/05 Javascript
浅探express路由和中间件的实现
2019/09/30 Javascript
vue实现导航标题栏随页面滚动渐隐渐显效果
2020/03/12 Javascript
layui实现显示数据表格、搜索和修改功能示例
2020/06/03 Javascript
[42:39]老党炸弹人试玩视频
2014/09/03 DOTA
python处理csv数据的方法
2015/03/11 Python
详解python做UI界面的方法
2019/02/27 Python
深入浅析CSS3中的Flex布局整理
2020/04/27 HTML / CSS
美国销售第一的智能手机和平板电脑保护壳:OtterBox
2017/12/21 全球购物
美体小铺波兰官方网站:The Body Shop波兰
2019/09/03 全球购物
大学旷课检讨书
2014/01/28 职场文书
大学生毕业求职自荐书范文
2014/02/04 职场文书
企业承诺书怎么写
2014/05/24 职场文书
社区党风廉政建设调研报告
2015/01/01 职场文书
postgresql 删除重复数据案例详解
2021/08/02 PostgreSQL
Netty分布式客户端处理接入事件handle源码解析
2022/03/25 Java/Android