i007.cc

i007.cc

优先队列-降维打击

Redis应用三:位图

在我们平时开发中,会有一些布尔型数据需要存取。比如你在很多睡眠软件里会看到早起打卡的活动,那么它就需要记录用户的签到记录,签了就是1,没签是0。如果使用普通的key/value,每个用户要记录365个,当用户千万、上亿的时候,无疑需要的存储空间时非常惊人的。

为了解决类似的这种需要存取大数据量的布尔类型问题,redis提供了位图数据结构,这样每天的签到记录只占据一个位,365个位只需要46个字节即可,这就大大节约了存储空间。

位图,其实就是byte数组,我们可以使用普通的 get/set 直接获取和设置整个位图的内容,也可以使用位图操作 getbit/setbit 等将 byte 数组看成「位数组」来处理。

基本使用

Redis 的位数组是自动扩展,如果设置了某个偏移位置超出了现有的内容范围,就会自动将位数组进行零扩充。

比如,我们要使用位操作将字符串设置为hello,那么我们要得到hello的二进制

public void toBinary(){
    String str = "hello";
    char[] strChar=str.toCharArray();
    String result="";
    for(int i=0;i<strChar.length;i++){
        result +=Integer.toBinaryString(strChar[i])+ " ";
    }
    System.out.println(result);
}

 

对于python用户来讲,无疑是非常方便的

>>> bin(ord('h'))
'0b1101000'   # 高位 -> 低位
>>> bin(ord('e'))
'0b1100101'
>>> bin(ord('l'))
'0b1101100'
>>> bin(ord('l'))
'0b1101100'
>>> bin(ord('o'))
'0b1101111'

 

这里我们拿h作为例子,h(01101000)需要转1的就是1/2/4位,位数组的顺序和字符的位顺序是相反的。

127.0.0.1:6379> setbit s 1 1
(integer) 0
127.0.0.1:6379> setbit s 2 1
(integer) 0
127.0.0.1:6379> setbit s 4 1
(integer) 0

 

这种位操作的方式是零存。零存就是使用setbit对位进行逐个设置,整存就是使用字符串一次性填充全部位数组,覆盖旧数组。当然还有零取和整取,道理类似,直接用set和get即可。

统计和查找

Redis 提供了两种指令:

  • bitcount 用来统计指定位置范围内 1 的个数
  • bitpos 用来查找指定范围内出现的第一个 0 或 1。

我们可以用bitcount统计用户一共签到额多少天,通过bitpos查找用户从那一天开始第一次签到。如果指定了范围参数[start,end],就可以统计在某个时间范围内用户签到了多少天,用户自某天以后的哪天开始签到。

127.0.0.1:6379> set a hello
OK
127.0.0.1:6379> bitcount a
(integer) 21
127.0.0.1:6379> bitcount a 0 0  # 第一个字符中 1 的位数
(integer) 3
127.0.0.1:6379> bitcount a 0 1  # 前两个字符中 1 的位数
(integer) 7
127.0.0.1:6379> bitpos a 0  # 第一个 0 位
(integer) 0
127.0.0.1:6379> bitpos a 1  # 第一个 1 位
(integer) 1
127.0.0.1:6379> bitpos a 1 1 1  # 从第二个字符算起,第一个 1 位
(integer) 9
127.0.0.1:6379> bitpos a 1 2 2  # 从第三个字符算起,第一个 1 位
(integer) 17

 

start 和 end 参数是字节索引,也就是说指定的位范围必须是8的倍数,而不能任意指定。如果我们想要计算出某个月内用户签到多少天,我们就得将这个月所福噶的字节内容全部取出来 (getrange 可以取出字符串的子串) ,再统计。

bitfield

前面我们用了setbit/getbit对位进行操作,如果我们想一次性操作多个位,那么就得用到了bitfield。

bitfield 有三个子指令,分别是 get/set/incrby,它们都可以对指定位片段进行读写,但是最多只能处理 64 个连续的位,如果超过 64 位,就得使用多个子指令,bitfield 可以一次执行多个子指令。

127.0.0.1:6379> bitfield w get u4 0 get u3 2 get i4 0 get i3 2
1) (integer) 6
2) (integer) 5
3) (integer) 6
4) (integer) -3
127.0.0.1:6379> bitfield w set u8 8 97  # 从第 8 个位开始,将接下来的 8 个位用无符号数 97 替换
1) (integer) 101
127.0.0.1:6379> get w
"hallo"
127.0.0.1:6379> set w hello
OK
127.0.0.1:6379> bitfield w incrby u4 2 1  # 从第三个位开始,对接下来的 4 位无符号数 +1
1) (integer) 11
127.0.0.1:6379> bitfield w incrby u4 2 1
1) (integer) 12
127.0.0.1:6379> bitfield w incrby u4 2 1
1) (integer) 13
127.0.0.1:6379> bitfield w incrby u4 2 1
1) (integer) 14
127.0.0.1:6379> bitfield w incrby u4 2 1
1) (integer) 15
127.0.0.1:6379> bitfield w incrby u4 2 1  # 溢出折返了
1) (integer) 0

 

关于位图,其实用的并不多,大家有个印象即可。

发表回复