在我们平时的开发过程中,会有一些boolean型数据需要存取,比如用户一年的签到记录,签了是1,没签是0,要记录365天。如果使用普通的key/value,每个用户要记录365个,当用户数上亿的时候,需要的存储空间是惊人的。
为了解决这个问题,redis提供了位图数据结构,这样每天的签到记录只占据一个位,365天就是365个位,46个字节(一个稍长一点的字符串)就可以完全容纳下,这就大大节约了存储空间。位图的最小单位是比特(bit),每个bit的取值只能是0或1,如下图所示。
位图不是特殊的数据结构,它的内容其实就是普通的字符串,也就是byte数组。我们可以使用普通的get/set直接获取和设置整个位图的内容,也可以使用位图操作getbit/setbit等将byte数组看成“位数组”来处理。
1、基本用法
redis的位数组是自动扩展的,如果设置了某个偏移位置超出了现有的内容范围,就会自动将位数组进行零扩充。
接下来我们使用位操作将字符串设置为hello(不是直接使用set指令),首先我们需要得到hello的ascii码,用python命令行可以很方便地得到每个字符的ascii码的二进制值。
接下来我们使用redis-cli设置第一个字符,也就是位数组的前8位,我们只需要设置为1的位,h字符只有1/2/4位需要设置,e字符只有9/10/13/15位需要设置。值得注意的是位数组的顺序和字符的位顺序是相反的,如下图所示。
2、统计和查找
redis提供了位图统计指令bitcount和位图查找指令bitpos。bitcount用来统计指定位置范围内1的个数,bitpos用来查找指定范围内出现的第一个0或1。
比如我们可以通过bitcount统计用户一共签到了多少天,通过bitpos指令查找用户从哪一天开始第一次签到。如果指定了范围参数 [start, end],就可以统计在某个时间范围内用户签到了多少天,用户自某天以后的哪天开始签到。
遗憾的是,start和end参数是字节索引,也就是说指定的位范围必须是8的倍数,而不能任意指定。
3、魔术指令bitfield
前面我们设置(setbit)和获取(getbit)指定位的值都是单个位的,如果要一次操作多个位,就必须使用管道来处理。
不过redis在3.2版本以后新增了一个功能强大的指令bitfield,有了这条指令,不用管道也可以一次进行多个位的操作。
bitfield有三个子指令,分别是get、set、incrby,它们都可以对指定位片段进行读写,但是最多只能处理64个连续的位,如果超过64位,就得使用多个子指令,bitfield可以一次执行多个子指令。