1、简介

Bitmaps 称为位图,它不是一种数据类型。网上很多视频教程把Bitmaps称为数据类型,应该是不正确的。Bitmaps 是Redis提供给使用者用于操作位的“数据类型”。它主要有如下的基本特性:

  • Bitmaps 不是数据类型,底层就是字符串(key-value),byte数组。我们可以使用普通的get/set直接获取和设值位图的内容,也可以通过Redis提供的位图操作getbit/setbit等将byte数组看成“位数组”来处理
  • Bitmaps 的“位数组”每个单元格只能存储0和1,数组的下标在Bitmaps中称为偏移量
  • Bitmaps设置时key不存在会自动生成一个新的字符串,如果设置的偏移量超出了现有内容的范围,就会自动将位数组进行零扩充


#yyds干货盘点#Bitmaps_偏移量

2 、基本操作

2.1 SETBIT key offset value

#yyds干货盘点#Bitmaps_偏移量_02

对key存储的字符串,设置或者清除指定偏移量上的位(bit),位的设置或者清除取决于value参数,0/1;当key不存在时,自动生成一个新的字符串。字符串会进行伸展确保value保存在指定的偏移量上。字符串进行伸展时,空白位置以0填充。

时间复杂度 :

O(1)

offset 范围:

0~2^32

返回值:

指定偏移量原来存储的位

案例:

使用Bitmaps来存储用户是否打卡,打卡记做1,未打卡为0,用户的id作为偏移量

假设存在10个用户,此时用户1、3、5、9、10打了卡,其他人未打卡,Bitmaps的初始化结果如下所示:

#yyds干货盘点#Bitmaps_时间复杂度_03

clock:20210806代表2021/08/06的打卡记录

#yyds干货盘点#Bitmaps_无符号整数_04

注意事项:

正式系统中,id肯定不会是0、1、2这种,而是以某一个数组开头,比如1000000000000001、1000000000000002这个时候非常容易导致偏移量的浪费,因此我们可以考虑通过计算减去一个合适的值后再设置偏移量,如果设置的Bitmaps偏移量过大,容易造成分配内存时间过长,Redis服务器被阻塞。


2.2 GETBIT key offset

#yyds干货盘点#Bitmaps_数组_05

获取指定偏移量上的位(bit),当offset比字符串长度大,或者key不存在,返回0;

时间复杂度:

O(1)

返回值:

字符串值指定偏移量上的位(bit)

案例:

clock:20210806代表2021/08/06的打卡记录

#yyds干货盘点#Bitmaps_时间复杂度_06


2.3 BITCOUNT key [start] [end]

计算给定字符串中,被设置为1的bit位的数量。start和end参数可以指定查询的范围,可以使用负数值。-1代表最后一个字节,-2代表倒是第二个字节。

注意:start和end是字节索引,因此每增加1 代表的是增加一个字符,也就是8位,所以位的查询范围必须是8的倍数。

时间复杂度:

O(N)

返回值:

被设置为1的位的数量

案例:

clock:20210806代表2021/08/06的打卡记录,此时一共11位,前8位置3个1,后3位中2个1

#yyds干货盘点#Bitmaps_数组_07

bitcount clock:20210806 0 0 表示第1个字符中1的个数

bitcount clock:20210806 1 1 表示第2个字符中1的个数

bitcount clock:20210806 0 1 表示第1和第2个字符中1的个数

#yyds干货盘点#Bitmaps_无符号整数_08


2.4 BITPOS key bit [start] [end]

返回第一个置为bit的二进制位的位置,默认检测整个Bitmaps,也可以通过start和end参数指定查询范围

注意:start和end是字节索引,因此每增加1 代表的是增加一个字符,也就是8位,所以位的查询范围必须是8的倍数。

时间复杂度:

O(N)

返回值:

整数回复

案例:

bitpos clock:20210806 0 表示第一个0的位置

bitpos clock:20210806 1 表示第一个1的位置

bitpos clock:20210806 1 0 0 表示第一个字符中,第一个1的位置

bitpos clock:20210806 1 1 1 表示第二个字符中,第一个1的位置

bitpos clock:20210806 1 0 1 表示第一个和第二个字符中,第一个1的位置

#yyds干货盘点#Bitmaps_字符串_09


2.5 BITOP operation destkey key [key …]

Redis的Bitmaps提供BITOP指令来对一个或多个(除了NOT操作)二进制位的字符串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

当字符串长度不一致是,较短的那个字符串所缺失的部分会被看作0,空的key也会被看作是包含0的字符串序列

时间复杂度:

O(N)

返回值:

位运算的结果(保存到destkey的字符串的长度和输入key中的最长的字符串的长度相等)

案例:

这里使用key1 1001和key2 1011进行上述四种操作

#yyds干货盘点#Bitmaps_无符号整数_10


BITOP AND destkey key [key ...]

运算规则:0&0=0; 0&1=0; 1&0=0; 1&1=1;

即:两位同时为“1”,结果才为“1”,否则为0

#yyds干货盘点#Bitmaps_偏移量_11

BITOP OR destkey key [key ...]

运算规则:0|0=0; 0|1=1; 1|0=1; 1|1=1;

即 :参加运算的两个对象只要有一个为1,其值为1

#yyds干货盘点#Bitmaps_字符串_12

BITOP XOR destkey key [key ...]

运算规则:0^0=0; 0^1=1; 1^0=1; 1^1=0;

即:参加运算的两个对象,如果两个相应位为“异”(值不同),则该位结果为1,否则为0

#yyds干货盘点#Bitmaps_时间复杂度_13

BITOP NOT destkey key

运算规则:取反

#yyds干货盘点#Bitmaps_无符号整数_14


2.6 BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]

2.1和2.2中的setbit和getbit都是对指定key的单个位的操作,如果需要对多个位同时操作,那么可以使用bitfield指令,bitfield有三个子指令,分别是get、set、incrby,它们可以对指定的片段进行读写,但是最多处理64个连续的位,超过64个连续的位,需要使用多个子指令,bitfield可以同时执行多个子指令(无符号整数只能返回63位)。


注意:

  • 使用 GET 子命令对超出字符串当前范围的二进制位进行访问(包括键不存在的情况), 超出部分的二进制位的值将被当做是 0 。
  • 使用 SET 子命令或者 INCRBY 子命令对超出字符串当前范围的二进制位进行访问将导致字符串被扩大, 被扩大的部分会使用值为 0 的二进制位进行填充。 在对字符串进行扩展时, 命令会根据字符串目前已有的最远端二进制位, 计算出执行操作所需的最小长度。

值操作子指令:

  • GET <type> <offset> —— 返回指定的二进制位范围
  • SET <type> <offset> <value> —— 对指定的二进制位范围进行设置,并返回它的旧值
  • INCRBY <type> <offset> <increment> —— 对指定的二进制位范围执行加法操作,并返回它的旧值。用户可以通过向 increment 参数传入负值来实现相应的减法操作

溢出策略子指令:

  • WRAP:回绕/折返(wrap around)-默认溢出策略,对于无符号整数来说, 回绕就像使用数值本身与能够被储存的最大无符号整数执行取模计算, 这也是 C 语言的标准行为。 对于有符号整数来说, 上溢将导致数字重新从最小的负数开始计算, 而下溢将导致数字重新从最大的正数开始计算。
  • SAT:饱和计算(saturation arithmetic),也可以理解为饱和截断,这种模式下下溢计算的结果为最小的整数值, 而上溢计算的结果为最大的整数值
  • FAIL:失败不执行,这种模式会拒绝执行那些导致上溢或者下溢的计算情况,返回nil表示计算未被执行。

需要注意的是, OVERFLOW 子命令只会对紧随着它之后被执行的 INCRBY 命令产生效果, 这一效果将一直持续到与它一同被执行的下一个 OVERFLOW 命令为止。 在默认情况下, INCRBY 命令使用 WRAP 方式来处理溢出计算。

i与u:

i表示有符号整数,u表示无符号整数。u4代表4位长的无符号整数,i8代表8位长的有符号整数。


案例:

测试数字为10100111

#yyds干货盘点#Bitmaps_无符号整数_15

bitfield key get u4 0 从第一个位开始取4个位,得到无符号数1010=10

#yyds干货盘点#Bitmaps_数组_16

bitfield key set u8 0 128 从第0个开始,将接下来的8位用无符号整数128替换,也就是10000000

#yyds干货盘点#Bitmaps_无符号整数_17

bitfield key incrby u4 2 1 从第2位开始对接下来的4位无符号数+1

#yyds干货盘点#Bitmaps_偏移量_18

bitfield key set u8 0 128 get u4 0 incrby u4 2 1 复合指令,是上面三者的组成,返回值是每个操作的子集,相当于管道操作

#yyds干货盘点#Bitmaps_数组_19