redis中的位图是什么意思

85次阅读
没有评论

共计 5114 个字符,预计需要花费 13 分钟才能阅读完成。

这篇文章将为大家详细讲解有关 redis 中的位图是什么意思,丸趣 TV 小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

位图

位图,即大量 bit 组成的一个数据结构(每个 bit 只能是 0 和 1),主要适合在一些场景下,进行空间的节省,并有意义的记录数据,

例如一些大量的 bool 类型的存取,一个用户 365 天的签到记录,签到了是 1,没签到是 0,如果用普通的 key/value 进行存储,当用户量很大的时候,需要的存储空间是很大的。

如果使用位图进行存储,一年 365 天,用 365 个 bit 就可以存储,365 个 bit 换算成 46 个字节(一个稍长的字符串),如此就节省了很多的存储空间,

位图的本质其实是一个普通的字符串,也就是 byte 数组,可以使用 get/set 直接获取和设置整个位图的内容,也可以使用 getbit/setbit 将 byte 数组看成 bit 数组来处理。【相关推荐:Redis 视频教程】

使用位操作设置字符串

正常设置字符串都使用 set 命令,下面我们使用 setbit 设置一下位数组,最后以获取字符串的形式获取,

首先我们获取一下 h、e 两个 ASCII 码使用二进制的表示如下,

可以看到 h 的二进制码是 01101000 , e 的二进制码是 01100101,我们只需要注意 bit 是 1 的位置,然后进行 setbit,

需要注意的是,位数组的顺序和字符的位顺序是反的,根据这个原则,我们算出 h 字符 每个 1 的位置分别是 1 /2/4, e 字符的则是 9/10/13/15,

所以我们将使用 setbit 设置一个位数组,并在每个位置上 (1/2/4/9/10/13/15) 设置对应的 1,

setbit data 1 1
setbit data 2 1
setbit data 4 1
setbit data 9 1
setbit data 10 1
setbit data 13 1
setbit data 15 1

零存整取

最后直接 get data 这个 key,会发现正好得到 he,

setbit + get 的组合称为 零存整取,零存就是一个 bit 一个 bit 的设置,整取就是通过 key 名字,直接 get 出来所有的数据,

同样,我们还可以进行 零存零取,整存零取,整存就是直接使用字符串设置整个位数组,零取则是通过 bit 的位置,进行 bit 的获取。

零存零取

可以看到,我们根据 setbit,对 key 叫做 w 的位数组进行 bit 设置,只设置了 1 /2/ 4 这 3 个位置的值为 1,下图中有 getbit w 3, 获取第三个位置的值,此时默认是 0,如果从业务角度触发,可以理解为,一共签到 4 天,第三天没有进行签到,

整存零取

下午所示,我们对 w 的这个 key,直接 set 了一个 h 字符,随后通过 getbit 获取 w 的位数组里的每个 bit,可以看到获取出来的内容和上面 h 字符的二进制内容相同 1/2/ 4 的位置是 1,其余是 0

注意

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

如果对应位的字节是不可打印字符,redis-cli 将会显示该字符的十六进制形式。

一个字节是 8 个 bit(位),要区分字节和位。

统计和查找 (bitcount/bitpos)

redis 提供了 统计指令 bitcount   和   位图查找指令 bitpos ,

bitcount 用来统计指定位置范围内 1 的个数,bitpos 用来查找指定范围内出现的第一个 0 或 1。

我们可以通过 bitcount 统计用户一共签到了多少天,通过 bitpos 指令查找用户从哪一天开始第一次签到,

如果指定了范围参数[start, end],就可以统计在某个时间范围内用户签到了多少天以及用户自某天以后的哪天开始签到,

但是需要注意的是,start 和 end 参数是字节索引,也就是说,指定的位范围必须是 8 的倍数,

而不能任意指定,所以我们无法直接计算某个月内用户签到了多少天,如果需要计算的话,

可以使用 getrange 命令取出该月覆盖的字节内容,然后在内存中进行统计,例如 2 月覆盖了 10-12 个字节,就使用 getrange w 8 12。

127.0.0.1:6379  set w hello 
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

bitfield

之前介绍的 setbit / getbit 指定位的值都是单个位,如果要一次操作多个位,就必须使用管道来处理,

在 redis3.2 以后,提供了 bitfield 指令,可以一次对多个位进行操作,bitfield 有三个子指令,分别是 get/set/incrby, 都可以对指定位片段进行读写,

但是最多只能处理 64 个连续的位,如果超过 64 位,就需要使用多个子指令,bitfield 可以一次执行多个子指令。

示例

下面对下图的位数组使用 bitfield 做一些操作

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

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

无符号数表示非负数,没有符号位,获取的位数组全部都是值,有符号数最多可以获取 64 位,

无符号数只能获取 63 位,因为 redis 协议中的 integer 是有符号数,最大 64 位,不能传递 64 位的无符号值,

如果超出位数限制,redis 就会告诉你参数错误。

上面的指令可以合并成一条指令, 可以看到得到的结果是一样的,

bitfield w get u4 0 get u3 2 get i4 0 get i3 2

set 修改

我们从第 9 个位开始,用 8 个无符号数替换已经存在的 8 个位,其实就是把第二个字符替换了,由 e 变成 a(它的 ASCII 码是 97),可以看到结果也变成了 hallo

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

incrby

incrby 对指定范围的位进行自增操作,即 ++,这可能会发生溢出,如果增加了正数,会出现上溢出,如果增加的是负数,会出现下溢出,

redis 默认的处理是折返,即如果出现了溢出,就将溢出的符号位丢掉,例如,如果是 8 位无符号数 255,加 1 后就全部变成 0,如果是 8 位有符号数 127,加 1 后就溢出变成 -128。

redis 中的位图是什么意思

依然根据 hello 字符,来演示一下 incrby

127.0.0.1:6379  set w hello
127.0.0.1:6379  bitfield w get u4 2 #  从第 3 位开始取 4 个无符号整数,第一次是 10
1) (integer) 10
127.0.0.1:6379  bitfield w incrby u4 2 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 # 到这里的时候,已经溢出折返成 0 了
1) (integer) 0

bitfield 指令提供溢出策略子指令 overflow,用户可以选择溢出行为,默认是折返(wrap),还可以选择失败(fail) 即报错不执行,还有饱和截断(sat) 即超过范围就停留在最大或最小值,

overflow 指令只影响接下来的第一条指令,这条指令执行完以后,溢出策略就会变成默认值 折返(wrap)。

饱和截断

127.0.0.1:6379  set w hello
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  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  set w hello
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  bitfield w overflow fail incrby u4 2 1
1) (nil)
127.0.0.1:6379  bitfield w overflow fail incrby u4 2 1
1) (nil)

get/set/incrby 一起执行

127.0.0.1:6379  bitfield w set u4 1 0 get u4 1 incrby u4 2 1
1) (integer) 0
2) (integer) 0
3) (integer) 1
127.0.0.1:6379  get w
 \x04ello

关于“redis 中的位图是什么意思”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-07-17发表,共计5114字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)