Redis 中的 BitMaps(位图)命令详解

Posted 共饮一杯无

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis 中的 BitMaps(位图)命令详解相关的知识,希望对你有一定的参考价值。

文章目录


Redis提供的Bitmaps这个“数据结构”可以实现对位的操作。它本身不是一种数据结构,实际上就是string(字符串)数据类型,但是它可以对字符串的位进行操作。可以把 Bitmaps想象成一个以位为单位的数组,数组中的每个单元只能存0或者1,数组的下标在bitmaps中叫做偏移量。单个 bitmaps 的最大长度是512MB,即2^32个比特位。
现代计算机用二进制位作为信息的基础单位,1个字节等位8位,例如 big 字符串是由3个字节组成,但实际在计算机存储时将其用二进制表示,big 分别对应的ASCII码分别是98、105、103,对应的二进制分别是01100010、01101001和01100111,如下图:

Bitmaps本身不是一种数据类型, 实际上它就是字符串,但是它可以对字符串的位进行操作。可以把 Bitmaps 想象成一个以位为单位的数组,数组的每个单元只能存储0和1,数组的下标在 Bitmaps 中叫做偏移量。

合理地使用位能够有效地提高内存使用率和开发效率,很适合用于签到这类场景。比如按月进行存储,一个月最多31天,那么我们将该月用户的签到缓存二进制就是 00000000000000000000000000000000,当某天签到将0改成1即可,而且 Redis 提供 对bitmap 的很多操作比如存储、获取、统计等指令,使用起来非常方便。

getbit key offset (对 key 所储存的字符串值,获取指定偏移量上的位(bit)。)


获取位图指定索引的值:

127.0.0.1:6379> flushall
OK
127.0.0.1:6379> set hello big
OK
127.0.0.1:6379> getbit hello 0
(integer) 0
127.0.0.1:6379> getbit hello 15
(integer) 1
127.0.0.1:6379> getbit hello 10
(integer) 1

setbit key offset value(对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。)


给位图指定索引设置值,返回该索引位置的原始值:

127.0.0.1:6379> flushall
OK
127.0.0.1:6379> set hello big
OK
127.0.0.1:6379> getbit hello 7
(integer) 0
127.0.0.1:6379> setbit hello 7 1
(integer) 0
127.0.0.1:6379> getbit hello 7
(integer) 1
127.0.0.1:6379> get hello
"cig"

bitcount key [start end](计算给定字符串中,被设置为 1 的比特位的数量)

获取位图指定范围(start到end,单位为字节,如果不指定就是获取全部)位值为1的个数:

默认情况下整个字符串都会被进行计数,通过指定额外的 start 或 end 参数,可以让计数只在特定的位上进行。start、end 是指 bit 组的字节的下标数,二者皆包含。

127.0.0.1:6379> flushall
OK
127.0.0.1:6379> set hello big
OK
127.0.0.1:6379> bitcount hello
(integer) 12
127.0.0.1:6379> getbit hello 7
(integer) 0
127.0.0.1:6379> setbit hello 7 1
(integer) 0
127.0.0.1:6379> bitcount hello 
(integer) 13
127.0.0.1:6379> bitcount hello 0 1
(integer) 8
127.0.0.1:6379> bitcount hello 0 2
(integer) 13
127.0.0.1:6379> bitcount hello 1 1
(integer) 4
127.0.0.1:6379> bitcount hello 1 2
(integer) 9
127.0.0.1:6379> bitcount hello 2 2
(integer) 5

Bitmap 对于一些特定类型的计算非常有效。假设现在我们希望记录自己网站上的用户的上线频率,比如说,计算用户 A 上线了多少天,用户 B 上线了多少天,诸如此类,以此作为数据,从而决定让哪些用户参加 beta 测试等活动——这个模式可以使用SETBIT和BITCOUNT来实现。
比如说,每当用户在某一天上线的时候,我们就使用 SETBIT ,以用户名作为 key ,将那天所代表的网站的上线日作为 offset 参数,并将这个 offset 上的为设置为 1 。举个例子,如果今天是网站上线的第 100 天,而用户 peter 在今天阅览过网站,那么执行命令SETBIT peter 100 1;如果明天 peter 也继续阅览网站,那么执行命令SETBIT peter 101 1,以此类推。
当要计算 peter 总共以来的上线次数时,就使用 BITCOUNT 命令:执行BITCOUNT peter,得出的结果就是 peter 上线的总天数。
前面的上线次数统计例子,即使运行 10 年,占用的空间也只是每个用户 10*365 比特位(bit),也即是每个用户 456 字节。对于这种大小的数据来说, BITCOUNT 的处理速度就像 GET 和 INCR 这种 O(1)复杂度的操作一样快。
如果你的 bitmap 数据非常大,那么可以考虑使用以下两种方法:

  1. 将一个大的 bitmap 分散到不同的 key 中,作为小的 bitmap 来处理。使用 Lua 脚本可以很方便地完成这一工作。
  2. 使用 BITCOUNT 的 start 和 end 参数,每次只对所需的部分位进行计算,将位的累积工作(accumulating)放到客户端进行,并且对结果进行缓存(caching)。

还有对于一些签到统计场景也一样非常有效,占用空间又小。

bitop and|or|not|xor destkey key [key…] (对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。)

做多个bitmap的and(交集)、or(并集)、not(非)、xor(异或)操作并将结果保存到 destkey 中:
语法:BITOP operation destkey key[key ...]
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 作为输入。

127.0.0.1:6379> flushall
OK
127.0.0.1:6379> set hello big
OK
127.0.0.1:6379> set world big
OK
127.0.0.1:6379> bitop and destkey hello world
(integer) 3
127.0.0.1:6379> bitop or destkey hello world
(integer) 3
127.0.0.1:6379> get destkey
"big"
127.0.0.1:6379> bitop not destkey hello
(integer) 3
127.0.0.1:6379> get destkey
"\\x9d\\x96\\x98"
127.0.0.1:6379> bitop xor destkey hello world
(integer) 3
127.0.0.1:6379> get destkey
"\\x00\\x00\\x00"

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

bitpos key bit [start] [end] (返回位图中第一个值为 bit 的二进制位的位置)

返回字符串里面第一个被设置为1或者0的bit位:

默认情况下整个字符串都会被检索一次,只有在指定start和end参数(指定start和end位是可行的),该范围被解释为一个字节的范围,而不是一系列的位。所以start=0 并且 end=2是指前三个字节范围内查找。

127.0.0.1:6379> flushall
OK
127.0.0.1:6379> set hello big
OK
127.0.0.1:6379> bitpos hello 1
(integer) 1
# 查找字符串里面bit值为0的位置
127.0.0.1:6379> bitpos hello 0
(integer) 0
# 从第1个字节开始的位置,查找字符串里面bit值为0的位置
127.0.0.1:6379> bitpos hello 0 1
(integer) 8
# 第2个字节开始的位置,查找字符串里面bit值为1的位置 
127.0.0.1:6379> bitpos hello 1 2
(integer) 17
# 第0个字节开始到第1个字节结束的位置,查找字符串里面bit值为1的位置
127.0.0.1:6379> bitpos hello 1 0 1
(integer) 1

BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL] (该命令将 Redis 字符串视为一个位数组,并且能够处理具有不同位宽和任意非(必要)对齐偏移量的特定整数字段。)

BITFIELD 命令可以将一个 Redis 字符串看作是一个由二进制位组成的数组, 并对这个数组中储存的长度不同的整数进行访问 (被储存的整数无需进行对齐)。 换句话说, 通过这个命令, 用户可以执行诸如 “对偏移量 1234 上的 5 位长有符号整数进行设置”、 “获取偏移量 4567 上的 31 位长无符号整数”等操作。 此外, BITFIELD 命令还可以对指定的整数执行加法操作和减法操作, 并且这些操作可以通过设置妥善地处理计算时出现的溢出情况。
BITFIELD 命令可以在一次调用中同时对多个位范围进行操作: 它接受一系列待执行的操作作为参数, 并返回一个数组作为回复, 数组中的每个元素就是对应操作的执行结果。
一次对多个位范围进行操作。bitfield 有三个子指令,分别是 get/set/incrby。每个指令都可以对指定片段做操作。

子命令:GET —— 返回指定的二进制位范围。
bitfield key get type offset

# 类型u代表无符号十进制,i代表带符号十进制
# 从偏移量offset=0开始取3位,获取无符号整数的值(将前3位二进制011转为无符号10进制返回)
127.0.0.1:6379> bitfield hello get u3 0
1) (integer) 3
# 从偏移量offset=0开始取4位,获取无符号整数的值(将前4位二进制0110转为无符号10进制返回)
127.0.0.1:6379> bitfield hello get u4 0
1) (integer) 6
# 从偏移量offset=0开始取5位,获取无符号整数的值(将前5位二进制01100转为无符号10进制返回)
127.0.0.1:6379> bitfield hello get u5 0
1) (integer) 12
# 从偏移量offset=1开始取2位,获取无符号整数的值(11前面补0,就是0011,转为无符号10进制返回)
127.0.0.1:6379> bitfield hello get u2 1
1) (integer) 3
# 从偏移量offset=0开始取2位,获取带符号整数的值(01前面补0,就是0001,转为带符号10进制返回)
127.0.0.1:6379> bitfield hello get i2 0
1) (integer) 1

子命令:SET —— 对指定的二进制位范围进行设置,并返回它的旧值。
bitfield key set type offset value

# 从偏移量offset=0开始取3位,设置为无符号的整数5并返回旧值
127.0.0.1:6379> bitfield hello set u3 0 5
1) (integer) 3
127.0.0.1:6379> bitfield hello get u3 0
1) (integer) 5
# 从偏移量offset=0开始取4位,设置为无符号的整数6并返回旧值
127.0.0.1:6379> bitfield hello set u4 0 6
1) (integer) 10
127.0.0.1:6379> bitfield hello get u4 0
1) (integer) 6
# 从偏移量offset=0开始取4位,设置为带符号的整数5并返回旧值
127.0.0.1:6379> bitfield hello set i4 0 5
1) (integer) 6
127.0.0.1:6379> bitfield hello get i4 0
1) (integer) 5

子命令:INCRBY —— 对指定的二进制位范围执行加法操作,并返回它的旧值。用户可以通过向 increment 参数传入负值来实现相应的减法操作。
bitfield key incrby type offset increment

# 从偏移量offset=0开始取4位,获取无符号整数的值
# 从偏移量offset=6开始取4位,设置为无符号的整数6 
# 从偏移量offset=4开始取4位,获取无符号整数的值并自增1
127.0.0.1:6379> bitfield hello get u4 0 set u4 4 6 incrby u4 4 1
1) (integer) 5
2) (integer) 2
3) (integer) 7

# 从偏移量offset=0开始取4位,获取无符号整数的值
127.0.0.1:6379>  bitfield hello get u4 0
1) (integer) 5
# 从偏移量offset=4开始取4位,设置为无符号整数6
127.0.0.1:6379>  bitfield hello set u4 4 6
1) (integer) 7
# 从偏移量offset=4开始取4位,获取无符号整数的值并自增1
127.0.0.1:6379> bitfield hello incrby u4 4 1
1) (integer) 7
# 从偏移量offset=4开始取4位,获取无符号整数的值
127.0.0.1:6379> bitfield hello get u4 4
1) (integer) 7
# 从偏移量offset=4开始取4位,获取无符号整数的值并自增1
127.0.0.1:6379> bitfield hello get u4 4 incrby u4 4 1
1) (integer) 7
2) (integer) 8
# 从偏移量offset=0开始取4位,获取无符号整数的值
127.0.0.1:6379> bitfield hello get u4 4
1) (integer) 8
127.0.0.1:6379>

实际运用案例可以看我这篇博客:CSDN是怎么实现用户签到,统计签到次数,连续签到天数等功能微服务的

参考:
https://www.lanmper.cn/redis/c479

本文内容到此结束了,
如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。
如有错误❌疑问💬欢迎各位指出。
主页共饮一杯无的博客汇总👨‍💻

保持热爱,奔赴下一场山海。🏃🏃🏃

以上是关于Redis 中的 BitMaps(位图)命令详解的主要内容,如果未能解决你的问题,请参考以下文章

redis | 十一redis之Bitmaps

redis特殊数据类型Bitmaps

redis入门到精通系列:redis高级数据类型详解(BitMaps,HyperLogLog,GEO)

redis入门到精通系列:redis高级数据类型详解(BitMaps,HyperLogLog,GEO)

七天玩转Redis | Day3Redis位图和GEO介绍与使用

Redis实现消息队列的4种方案