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

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

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

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

以上介绍了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> 

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Redis中3种特殊的数据类型(BitMap、Geo和HyperLogLog)

    前言 Reids 在 Web 应用的开发中使用非常广泛,几乎所有的后端技术都会有涉及到 Redis 的使用.Redis 种除了常见的字符串 String.字典 Hash.列表 List.集合 Set.有序集合 SortedSet 等等之外,还有一些不常用的数据类型,这里着重介绍三个.下面话不多说了,来一起看看详细的介绍吧. BitMap BitMap 就是通过一个 bit 位来表示某个元素对应的值或者状态, 其中的 key 就是对应元素本身,实际上底层也是通过对字符串的操作来实现.Redis 从

  • Redis基于Bitmap实现用户签到功能

    目录 功能分析 更多应用场景 总结 参考资料 很多应用上都有用户签到的功能,尤其是配合积分系统一起使用.现在有以下需求: 签到1天得1积分,连续签到2天得2积分,3天得3积分,3天以上均得3积分等. 如果连续签到中断,则重置计数,每月重置计数. 显示用户某月的签到次数和首次签到时间. 在日历控件上展示用户每月签到,可以切换年月显示. ... 功能分析 对于用户签到数据,如果直接采用数据库存储,当出现高并发访问时,对数据库压力会很大,例如双十一签到活动.这时候应该采用缓存,以减轻数据库的压力,Re

  • redis通过位图法记录在线用户的状态详解

    前言 在进入今天的主题前,先简单地解释下Redis中的位图到底是什么.Redis官方文档对于位图的介绍如下: 位图不是一个真实的数据类型,而是定义在字符串类型上的面向位的操作的集合.由于字符串类型是二进制安全的二进制大对象,并且最大长度是 512MB,适合于设置 2^32个不同的位. 位操作分为两组:常量时间单个位的操作,像设置一个位为 1 或者 0,或者获取该位的值.对一组位的操作,例如计算指定范围位的置位数量. 位图的最大优势是有时是一种非常显著的节省空间来存储信息的方式.例如,在一个系统中

  • Redis高级数据类型Hyperloglog、Bitmap的使用

    前言 很多小伙伴在面试中都会被问道 Redis的常用数据结构有哪些? 可能很大一部分回答都是 string.hash.list.set.zset.当然啦,这个答案肯定是没有错的,但是相信这个答案,面试官已经听的耳朵都起茧了. 本身我们选择的这个行业竞争就极强,学历拼不过难道还要知识都拼不过吗??? 希望进来的小伙伴能好好看完这篇文章,也希望你以后的回答能是 常用的数据结构有string.hash.list.set.zset,但我平时可能还会用到 Hyperloglog和Bitmap.相信面试官听

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

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

  • Redis位图bitmap操作

    目录 定义 强势点 SETBIT 命令 GETBIT 命令 BITCOUNT 命令 应用场景案例 定义 1.位图不是真正的数据类型,它是定义在字符串类型中2.一个字符串类型的值最多能存储512M字节的内容,位上限:2^32 # 1MB = 1024KB # 1KB = 1024Byte(字节) # 1Byte = 8bit(位) 强势点 可以实时的进行统计,极其节省空间.官方在模拟1亿2千8百万用户的模拟环境下,在一台MacBookPro上,典型的统计如“日用户数”的时间消耗小于50ms, 占用

  • PHP使用redis位图bitMap 实现签到功能

    一.需求 记录用户签到,查询用户签到 二.技术方案 1.使用mysql(max_time字段为连续签到天数) 思路: (1)用户签到,插入一条记录,根据create_time查询昨日是否签到,有签到则max_time在原基础+1,否则,max_time=0 (2)检测签到,根据user_id.create_time查询记录是否存在,不存在则表示未签到 2.使用redis位图功能 思路: (1)每个用户每个月单独一条redis记录,如00101010101010,从左往右代表01-31天(每月有几

  • 浅谈python处理json和redis hash的坑

    1.使用MySQLdb读取出来的数据是unicode字符串,如果要写入redis的hash中会变成 "{u'eth0_outFlow': 2.5, u'eth1_inFlow': 3.44}" 无法使用json.loads,需要提前将unicode转成str: str(eth0_outFlow) 2.单引号包围的key不是规范的json格式 "{'eth0_outFlow': 2.5, 'eth1_inFlow': 3.44}" 需要转成规范的格式才能使用json.

  • 浅谈do {...} while (0) 在宏定义中的作用

    如果你是一名C程序员,你肯定很熟悉宏,它们非常强大,如果正确使用可以让你的工作事半功倍.然而,如果你在定义宏时很随意没有认真检查,那么它们可能使你发狂,浪费N多时间.在很多的C程序中,你可能会看到许多看起来不是那么直接的较特殊的宏定义. 下面就是一个例子: #define __set_task_state(tsk, state_value) \ do { (tsk)->state = (state_value); } while (0) 在Linux内核和其它一些著名的C库中有许多使用do{..

  • 浅谈Java 将图片打包到jar中的路径问题

    Eclipse使用导出Jar包后打开加载不了图像? 出现这种问题的原因大多是因为路径的问题,往往是在项目中运行正常,但是一旦打包成Jar后就不能正常显示了,下面总结一下解决此类问题的方法: 一.通过使用外部资源文件的方式解决 把项目打包生成Jar后,再新建一文件夹,把项目中所有使用的图片文件和生成Jar文件放在该目录下,只要代码对图片文件的引用路径正确,如:ImageIcon image=new ImageIcon(SwingResourceManager.getImage("icons/log

  • 浅谈如何使用vb.net从数据库中提取数据

    1.设置从Model中的Sub Main 启动 2.程序结构 3.Model1 Imports System.Windows.Forms.Application Module Module1 Sub Main() 'form1 是测试多文档窗口 'Dim frm1 As New Form1() 'frm1.Show() Dim formStudentSysMain As New FormStudentSysMain() formStudentSysMain.Show() Do While Tru

  • 浅谈java运用注解实现对类中的方法检测的工具

    创建自定义注解 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Test { } 建立测试类 public class UserTest { @Test public void testInsert() { User user = null; System.out.println(user.getUsername()); } @Test public void testQuery(

  • 浅谈如何使用python抓取网页中的动态数据实现

    我们经常会发现网页中的许多数据并不是写死在HTML中的,而是通过js动态载入的.所以也就引出了什么是动态数据的概念,动态数据在这里指的是网页中由Javascript动态生成的页面内容,是在页面加载到浏览器后动态生成的,而之前并没有的. 在编写爬虫进行网页数据抓取的时候,经常会遇到这种需要动态加载数据的HTML网页,如果还是直接从网页上抓取那么将无法获得任何数据. 今天,我们就在这里简单聊一聊如何用python来抓取页面中的JS动态加载的数据. 给出一个网页:豆瓣电影排行榜,其中的所有电影信息都是

  • 浅谈vue使用axios的回调函数中this不指向vue实例,为undefined

    今天在vue-cli脚手架搭建的项目中使用axios时,遇到无法解析this.$route的报错信息,最后发现是作用域的问题. 1.解决方法:使用 => 原代码: axios.get('/user', { params: { ID: 12345 } }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }); 修改为: axios.get('/u

随机推荐