Redis位图

1,354 阅读3分钟

简介

对于一些场景,比如统计用户一年签到次数,用 0 和 1 标识是否签到,要记录365天,通常使用key、value这种数据结构,当用户量很大是,存储空间也是很大的。

image.png

上图是表示一位用户 10 天内来网站的签到次数,1 代表签到,0 代表未签到,这样可以很轻松地统计出用户的活跃程度。相比于直接使用字符串而言,位图中的每一条记录仅占用一个 bit 位,从而大大降低了内存空间使用率。

Redis 官方也做了一个实验,他们模拟了一个拥有 1 亿 2 千 8 百万用户的系统,然后使用 Redis 的位图来统计“日均用户数量”,最终所用时间的约为 50ms,且仅仅占用  16 MB内存。

基本原理

位图操作的优势,相比于字符串而言,它不仅效率高,而且还非常的节省空间。

实际上,位图并不是 Redis 提供的一种新的数据类型,它是字符串类型的扩展。所以位图的命令可以直接使用在字符串类型的键上,位图命令操作的键也可以被字符串类型命令操作。 比如,现有一个字符串键 foo:

redis> set foo bar

1 个字节由 8 个二进制位组成,所以 foo 键的二进制形式就是:

image.png

常用的命令

1. 基本命令 SETBIT、GETBIT

SETBIT可以为位图指定偏移量上的二进制位设置值,offset 必须大于等于 0,value 只能是 0 或 1。使用 GETBIT 命令可以获取位图指定偏移量上的二进制位的值。此命令的时间复杂度都是 O(1)

零存零取

127.0.0.1:6379> setbit s 1 1
(integer) 0
127.0.0.1:6379> getbit w 1  # 获取某个具体位置的值 0/1
(integer) 1

整存零取

127.0.0.1:6379> set w h  # 整存
(integer) 0
127.0.0.1:6379> getbit w 1
(integer) 1
2. 统计和查找 BITCOUNT、BITPOS

通过 BITCOUNT 命令可以统计位图中值为 1 的二进制位数量。此命令的时间复杂度是 O(n)。 通过执行 BITPOS 命令,在位图中查找第一个被设置为指定值的二进制位,并返回这个二进制位的偏移量。

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

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

image.png

127.0.0.1:6379> set w hello
OK
127.0.0.1:6379> bitfield w get u4 0  # 从第一个位开始取 4 个位,结果是无符号数 (u)
(integer) 6
127.0.0.1:6379> bitfield w get u3 2  # 从第三个位开始取 3 个位,结果是无符号数 (u)
(integer) 5
127.0.0.1:6379> bitfield w get i4 0  # 从第一个位开始取 4 个位,结果是有符号数 (i)
1) (integer) 6
127.0.0.1:6379> bitfield w get i3 2  # 从第三个位开始取 3 个位,结果是有符号数 (i)
1) (integer) -3

负数与二进制换转方法。

  • 除去符号位减1
  • 除去符号位,按位取反;结果就是负数的源码;
  • 源码转成相应的十进制

使用场景

用户行为记录、简单统计类