Redis学习手册7—数据结构之位图

Redis的位图

Redis的位图(bitmap)是由多个二进制位组成的数组,数组中的每个二进制位都有与之对应的偏移量(也称索引),用户通过这些偏移量可以对位图中指定的一个或多个二进制位进行操作。

下图展示了一个包含8个二进制位的位图示例,这个位图存储的值为 10010100:
在这里插入图片描述

Redis位图功能

Redis为位图提供了一系列操作命令,通过这些命令,用户可以:

  • 为位图指定偏移量上的二进制位设置值,或者获取位图指定偏移量上的二进制位的值。
  • 统计位图中有多少个二进制位被设置成了 1 。
  • 查找位图中第一个被设置为指定值的二进制为并返回它的偏移量。
  • 对一个或多个位图执行逻辑并逻辑或逻辑异或以及逻辑非运算。
  • 将指定类型的整数存储到位图中。

位图命令速查表

下表列举了Redis为位图类型键提供的一系列命令,用法及参数以及它们的简要说明:

命令用法及参数说明
SETBITSETBIT bitmap offset value为位图指定偏移量上的二进制位设置值
GETBITGETBIT bitmap offset获取指定偏移量上的二进制位的值
BITCOUNTBITCOUNT bitmap [start end]统计位图中值为 1 的二进制位数量
BITPOSBITPOS bitmap value [start end]查找第一个指定值的二进制位,返回该二进制位的偏移量
BITOPBITOP operation result_key bitmap [bitmap ...]对一个或多个位图执行指定的二进位运算,并将结果保存到指定的键中
BITFIELDBITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]在位图中存储整数值

命令详解

SETBIT命令

通过使用SETBIT命令,可以为位图指定偏移量上的二进制位设置值:

SETBIT bitmap offset value

SETBIT命令在成功设置之后,将返回二进制位被设置值之前的旧值作为返回值。

127.0.0.1:6379> SETBIT bitmap001 0 1
(integer) 0  -- 二进制位原来的值为 0
127.0.0.1:6379> SETBIT bitmap001 3 1
(integer) 0
127.0.0.1:6379> SETBIT bitmap001 5 1
(integer) 0

下图展示了上述命令的执行过程:
在这里插入图片描述

位图的扩展

当用户执行SETBIT命令尝试对一个位图进行设置的时候,如果位图不存在,或者位图当前的大小无法满足用户想要执行的设置操作,那么Redis将对被设置的位图进行扩展,使得位图可以满足用户的设置请求。

因为Redis对位图的扩展操作是以字节为单位进行的,所以扩展之后的位图包含的二进制位数量可能会比用户要求的稍微多一些,并且在扩展位图的同时,Redis还会将所有未被设置的二进制位的值初始化为 0 。

比如执行以下命令,对尚未存在的位图bitmap002在偏移量10之上的二进制位进行设置:

127.0.0.1:6379> SETBIT bitmap002 10 1
(integer) 0

那么Redis创建出的位图并不会只有11个二进制位,而是有两个字节共16个二进制位,如下图所示:
在这里插入图片描述
上图可以看出,除了偏移量10之上的二进制位,其他未被设置的二进制位默认都是 0 。

注意: 与其他Redis命令可以使用负数作为偏移量的做法不同,SETBIT命令只能使用正数偏移量,尝试输入负数将引发错误。

复杂度: O ( 1 ) O(1) O(1)
版本要求:SETBIT命令从Redis 2.2.0版本开始可用。

GETBIT命令

使用GETBIT命令可以获取指定偏移量上的二进制的值:

GETBIT bitmap offset

SETBIT命令一样,GETBIT命令只接受正数偏移量。

127.0.0.1:6379> GETBIT bitmap001 0
(integer) 1

如果输入的偏移量超过了位图目前拥有的最大偏移量,将返回 0 。

复杂度: O ( 1 ) O(1) O(1)
版本要求:GETBIT命令从Redis 2.2.0版本开始可用。

BITCOUNT命令

BITCOUNT命令可以用来统计位图中值为 1 的二进制位数量:

BITCOUNT bitmap
127.0.0.1:6379> BITCOUNT bitmap001
(integer) 3

在默认情况下,BITCOUNT命令将统计位图中包含的所有字节中的二进制位,但是我们也可以通过可选范围参数 startend来指定统计的字节范围:

BITCOUNT bitmap [start end]

需要注意的是,startend参数与之前介绍的 offset参数并不相同,这两个参数是用来指定 字节偏移量 而不是二进制位偏移量。

位图的字节偏移量与Redis其他数据结构的偏移量一样,都是从0开始的:位图第一个字节的偏移量为0,第二个字节的偏移量为1,以此类推。

下图所示为bitmap003,使用以下命令来统计第一个字节里有多少个二进制位被设置成了 1 :
在这里插入图片描述

127.0.0.1:6379> BITCOUNT bitmap003 0 0  -- 统计第一个字节里的二进制位
(integer) 6
127.0.0.1:6379> BITCOUNT bitmap003 2 2  -- 统计第二个字节里的二进制位
(integer) 4

BITCOUNT命令的startend参数的值除了可以是正数,也可以是负数,与Redis其他支持负数索引的命令类似。

复杂度: O ( N ) O(N) O(N),其中 N N N为被统计的字节的数量。
版本要求:BITCOUNT命令从 Redis 2.6.0版本开始可用。

BITPOS命令

使用BITPOS命令可以查找位图中第一个被设置为指定值的二进制位,并返回该二进制位的偏移量:

BITPOS bitmap value

比如,通过执行以下命令,我们可以知道位图bitmap003第一个被设置为 1 的二进制位所在的偏移量:

127.0.0.1:6379> BITPOS bitmap003 1
(integer) 0   -- 位图第一个被设置为1的二进制位的偏移量为 0

同样的,在默认情况下,BITPOS命令的查找范围将覆盖位图包含的所有二进制位,但是,我们也可以使用可选范围参数 startend来指定需要查找的字节范围:

BITPOS bitmap value [start end]

其中 startend的含义与BITCOUNT命令的一样,指的都是位图的字节偏移量,而不是二进制位偏移量,同样支持负数偏移量。

对于不存在的位图或者一个所有位都被设置为0的位图中查找值为 1 的二进制位时,会返回 -1 。

127.0.0.1:6379> BITPOS not-exists-bitmap 1
(integer) -1
127.0.0.1:6379> BITPOS all-0-bitmap 1
(integer) -1

如果在一个所有位都被设置为 1 的位图中查找值为 0 的二进制位,那么 BITPOS命令将返回位图最大偏移量加上 1 作为结果。

127.0.0.1:6379> BITPOS bitmap-8bits-all-1  0
(integer) 8

复杂度: O ( N ) O(N) O(N),其中 N N N为查找涉及的字节数量。
版本要求:BITPOS命令从 Redis 2.8.7版本开始可用。

BITOP命令

使用BITOP命令可以对一个或多个位图执行指定的二进位运算,并将结果保存到指定的键中:

BITOP operation result_key bitmap [bitmap  ...]

其中 operation参数的值可以是:ANDORXORNOT中的任意一个,这4个值分别对应:逻辑并逻辑或逻辑异或逻辑非。其中ANDORXOR这3种运算允许用户使用任意数量的位图作为输入,而 NOT运算只允许使用一个位图作为输入。BITOP命令将结果保存在指定的键中,会返回被存储位图的字节长度。

127.0.0.1:6379> BITOP AND and_result bitmap001 bitmap002 bitmap003
(integer) 1
127.0.0.1:6379> BITOP OR or_result bitmap001 bitmap002 bitmap003
(integer) 1
127.0.0.1:6379> BITOP XOR xor_result bitmap001 bitmap002 bitmap003
(integer) 1
127.0.0.1:6379> BITOP NOT not_result bitmap001
(integer) 1

BITOP命令在对两个不同长度的位图执行运算时,会将长度较短的那个位图中不存在的二进制位的值看作是 0 。

复杂度: O ( N ) O(N) O(N),其中 N N N为计算涉及的字节数总量。
版本要求:BITOP命令从Redis 2.6.0版本开始可用。

BITFIELD命令

BITFIELD命令允许用户在位图中的任意区域(field)存储指定长度的整数值,并对这些整数执行加法或减法操作。
BITFIELD命令支持SETGETINCRBYOVERFLOW这4个子命令。

根据偏移量对区域进行设置

通过使用BITFIELD命令的SET子命令,用户可以在位图的指定偏移量offset上设置一个type类型的整数值value

BITFIELD bitmap SET type offset value

其中:

  • offset 参数用于指定设置的起始偏移量,这个偏移量从0开始计算,偏移量为0表示设置从位图的第一个二进制位开始。如果被设置的值长度不止一位,那么设置将自动延伸至之后的二进制位。
  • type 参数用于指定被设置的值的类型,这个参数的值需要以iu为前缀,后跟被设置值的位长度,其中i表示被设置的值为有符号整数,u表示无符号整数。BITFIELD的各个子命令目前最大的能够对64位长的有符号整数(i64)和63位长的无符号整数(u63)进行操作。
  • value参数用于指定被设置的整数值,这个值的类型应该与type参数指定的类型一致。如果给定的值长度超过了type参数指定的类型,那么SET命令将根据type参数指定的类型截断给定值。
127.0.0.1:6379> bitfield bitmap set u8 0 128
(integer) 0

BITFIELD命令允许用户在一次调用中执行多个子命令,如下所示:

127.0.0.1:6379> BITFIELD bitmap SET u8 0 123 SET i32 20 10086 SET i64 188 123456789
1) (integer) 198
2) (integer) 0
3) (integer) 0

SET子命令的特点:

  • 设置可以在位图的任意偏移量上进行,被设置的区域之间不必是连续的,也不需要进行对齐,未被设置的二进制位自动初始化为0 。
  • 在同一个位图中可以存储多个不同类型的和不同长度的整数。

从节约资源的角度考虑,一般情况应该如下使用位图:

  • 以对齐的方式使用位图,并且让位图尽可能的紧凑,避免包含过多的空洞。
  • 每个位图只存储一种类型的整数,并使用int-8bitunsigned-16bit这样的键名作为前缀。

根据索引对区域进行设置

除了根据偏移量对位图进行设置之外,SET子命令还允许用户根据给定类型的长度,对位图上指定索引上存储的整数进行设置:

BITFIELD bitmap SET type #index value

当位图中存储的都是相同类型的整数时,使用这种设置方法将给用户带来非常大的便利,因为这种方法允许用户直接对位图上
指定索引的整数进行设置,而不比知道整数值具体存储在位图的哪个偏移量上。

假设现在有一个位图,存储着多个8位长度的无符号整数,现在想要在它的第133个8位无符号整数的值设置为22。如果使用SET子命令的偏移量设置格式,就需要先使用算是 ( 133 − 1 ) ∗ 8 (133-1)*8 (1331)8 计算出第133个8位无符号整数在位图中的起始偏移量1056,再执行以下命令:

127.0.0.1:6379> BITFIELD  bitmap  SET u8 1056  22

很明显,这种手动计算偏移量然后进行设置的做法非常麻烦也容易出错,下面是使用索引的方式:

127.0.0.1:6379> BITFIELD bitmap SET u8 #132 22

注意: 因为SET子命令接受的索引是从0开始计算的,所以上面的子命令使用的索引是132,而不是133

获取区域存储的值

使用BITFIELD命令的GET子命令可以获取指定区域存储的整数值:

BITFIELD  bitmap  GET  type  offset   -- 通过偏移量获取
BITFIELd bitmap GET type #index   -- 通过索引获取
127.0.0.1:6379> BITFIELD bitmap GET u8 0
(integer) 128

执行加法或减法

除了设置和获取整数值以外,BITFIELD命令还可以对位图存储的整数值执行加法或减法操作:

BITFIELD bitmap INCRBY type offset increment   -- 指定偏移量上的整数加法
BITFIELD bitmap INCRBY type #index increment  -- 指定索引上的整数加法

执行加法的操作与字符串类型键的INCRBY命令一样,只要传入负数增量就可以实现减法操作。

处理溢出

BITFIELD命令除了可以使用INCRBY子命令执行加法或减法操作以为,还可以使用OVERFLOW子命令去控制INCRBY子命令在计算溢出时的行为:

BITFIELD bitmap [...] OVERFLOW WRAP|SAT|FAIL [...]

OVERFLOW子命令的参数解释如下:

参数名说明
WRAP表示使用回绕方式处理溢出,这也是C语言默认的处理方式,在这一模式下,向上溢出的整数值将从类型的最小值开始重新计算,而向下溢出的整数值则会从类型的最大值开始重新计算
SAT表示使用饱和运算方式处理溢出,在这一模式下,向上溢出的整数值将被设置为类型的最大值,而向下溢出的整数值则被设置为类型的最小值
FAIL表示让INCRBY子命令在检测到计算会引发溢出时拒绝执行计算,并返回空值表示计算失败

如果在执行BITFIELD命令时没有指定OVERFLOW处理溢出的方式,那么默认将使用WRAP方式处理计算溢出。

注意: 因为OVERFLOW子命令只会对同一个BITFIELD命令调用中排在它后面的那些INCRBY子命令产生效果,因此用户必须把OVERFLOW子命令放在它想要影响的INCRBY子命令之前。

使用位图存储整数的原因

在一般情况下,当用户使用字符串或散列去存储整数的时候,Redis都会为被存储的整数分配一个long类型的值,并使用对象去包裹这个值,然后再把对象关联到数据库或散列中。

与此相反,BITFIELD命令允许用户自行指定被存储数据的类型,并且不会使用对象去包裹这些整数,因此当我们想要存储长度超过long类型的整数,并且希望尽可能的减少对象包裹带来的内存消耗时,就可以考虑使用位图来存储整数。

复杂度: O ( N ) O(N) O(N),其中 N N N为用户给定的子命令数量。
版本要求:BITFIELD命令从Redis 3.2.0版本开始可用。

上一篇:Redis学习手册6—数据结构之HyperLogLog

下一篇:Redis学习手册8—数据结构之地理坐标

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值