简介
对于一些场景,比如统计用户一年签到次数,用 0 和 1 标识是否签到,要记录365天,通常使用key、value这种数据结构,当用户量很大是,存储空间也是很大的。
上图是表示一位用户 10 天内来网站的签到次数,1 代表签到,0 代表未签到,这样可以很轻松地统计出用户的活跃程度。相比于直接使用字符串而言,位图中的每一条记录仅占用一个 bit 位,从而大大降低了内存空间使用率。
Redis 官方也做了一个实验,他们模拟了一个拥有 1 亿 2 千 8 百万用户的系统,然后使用 Redis 的位图来统计“日均用户数量”,最终所用时间的约为 50ms,且仅仅占用 16 MB内存。
基本原理
位图操作的优势,相比于字符串而言,它不仅效率高,而且还非常的节省空间。
实际上,位图并不是 Redis 提供的一种新的数据类型,它是字符串类型的扩展。所以位图的命令可以直接使用在字符串类型的键上,位图命令操作的键也可以被字符串类型命令操作。 比如,现有一个字符串键 foo:
redis> set foo bar
1 个字节由 8 个二进制位组成,所以 foo 键的二进制形式就是:
常用的命令
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 可以一次执行多个子指令。
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
- 除去符号位,按位取反;结果就是负数的源码;
- 源码转成相应的十进制
使用场景
用户行为记录、简单统计类