严格来说Bitmaps并不是一个新的数据结构,而是一个符合特殊约定的字符串。是set、get等一系列字符串操作的一种扩展,与其不同的是,它提供的是位级别的操作,从这个角度看,我们也可以把它当成是一种位数组、位向量结构。Redis从2.2.0版本开始新增了setbit ,getbit , b itcount等几个bitmap相关命令。

BitMaps位图原理:

下面看一下bitmaps底层实际是怎样操作的。首先我们知道redis存储的是二进制文件,假设我们此时存入一个字符串“abc”

set bitmaps_test abc

abc对应二进制:
01100001 01100010 01100011

至于这个二进制数如何得来其实很简单,a对应ASCII码中的97,再将97转换为二进制就是:1100001 。有兴趣的可以去码表对照一下:
https://baike.sogou.com/v53369.htm?fromTitle=ASCII

下面我们将使用bitmaps对字符串abc进行操作来看一下会是什么结果,假设我们将a的第6位设为1后a是否会变成c呢?(这里不要误以为以为是6就是第六个数字,索引下标是从0开始的此书6对应第7个bit位)

setbit bitmaps_test 6 1

get bitmaps_test

输出结果:"cbc"
setbit bitmaps_test 22 0

get bitmaps_test

输出结果:"cba"

上述就是一个简单的位图操作,除了setbit外redis还为我们提供了其它一些有用的功能

getbit:

返回指定位置处的二进制信息

getbit bitmaps_test 7
输出结果: "1"

bitop:

完整的命令参数是: bitop operation destkey key [key…]

其中operation是位操作类型,支持and、or、not、xor,分别就是与、或、非、异或。destkey是用来存储计算结果的变量,key是参与运算是变量,可以指定多个。

BITOP AND destkey srckey1 srckey2 srckey3 ... srckeyN ,对一个或多个 key 求逻辑并,并将结果保存到 destkey 
BITOP OR destkey srckey1 srckey2 srckey3 ... srckeyN,对一个或多个 key 求逻辑或,并将结果保存到 destkey 
BITOP XOR destkey srckey1 srckey2 srckey3 ... srckeyN,对一个或多个 key 求逻辑异或,并将结果保存到 destkey 
BITOP NOT destkey srckey,对给定 key 求逻辑非,并将结果保存到 destkey

bitcount key [start end]:

统计字符串被设置为1的bit数。需要注意的是,这里的start和end并不是位偏移,而是以字节(8位)为单位来偏移的,比如BITCOUNT foo 0 1是统计key为foo的字符串中第一个到第二个字节中bit为1的总数。

bittops key bit [start] [end]:

在默认情况下,命令将检测到的整个位图,但用户也可以通过可选的start参数和end参数指定要检测的范围,返回位图中第一个值为bit的二进制位的位置

bitfield:

redis3.2后新增了一个bitfield命令,可以一次对多个位进行操作.这个指令有三个子指令,get,set,incrby,都可以对指定位片段进行读写,但最多只能处理64个连续的位,如超过64位,则要使用多个子指令,bitfield可以一次执行多个子指令。

set bitfield——test hello

hello对应二进制:
0110 1000  
0110 0101
0110 1100
0110 1100
0110 1111
bitfield bitfield_test get u4 0    #从第一个位开始取4个位(0110),结果为无符号数(u)
结果:6
bitfield bitfield_testget u3 2    #从第三个位开始取3个位(101),结果为无符号数(u)
结果:5
bitfield bitfield_testget get i4 0   #从第一个位开始取4个位(0110),结果为有符号数(i)
结果:6
因为结果为有符号数所以,第一位符号位为0代表是正数。110为6,结果直接为6
bitfield bitfield_testget get i3 2   #从第三个位开始取3个位(101),结果为有符号数(i)
结果:-3
取到的结果首位为1代表是负数,01需要取补码运算。01取反为10,10+1为11。11十进制为3,因为符号位为1所以最终结果为-3

如果想将 二进制 转为 十进制,必须使用 二进制的原码

incrby:

incrby对指定范围的位进行自增操作,如操作后有数据溢出,redis的处理是折返,即将益处的符号位丢掉,比如:8位的无符号数255,加1后变为0,而8位的有符号数127,加1后成-128.

也可以选择溢出行为,还可以选择 失败(fail,报错不执行), 折返( wrap ), 饱和截断(sat, 超过了范围就停留在最大或最小值)

使用场景:

首先bitmap本质是string,string的最大长度为512m,所以它可以表示2^32=4294967296个不同的位。基本不用担心位数不够的问题

就拿最常见的点赞功能,再不需要记录点赞顺序的情况下我们就可以使用bitmap优雅的实现。我们使用不同位对应用户ID,当ID为1000的用户点赞了,给门就可以将offset为1000的位设置为1来标记当前点赞用户。当我们需要统计点赞总数时只需要使用bitcount 统计出所有为1的数即可实现。

其余的如一些用户签到、统计活跃用户、用户在线状态、用户访问量等的统计功能都十分容易使用bitmaps实现并且性能十分可观,可以节省很多内存。缺点就是当偏移量非常大的时候可能会花费更多的时间,不过总的来说还是个不错的解决方案。