语法

1
BITCOUNT key [start end [BYTE | BIT]]

可用版本

≥ 2.6.0

时间复杂度

$O(N)$

ACL类别

@read, @bitmap, @slow

计算给定字符串中,被设置为 1 的比特位的数量。

默认情况下,给定的整个字符串都会被进行计数,通过指定额外的 startend 参数,可以让计数只在特定的位上进行。

startend 参数的设置和 GETRANGE key start end 命令类似,都可以使用负数值: 比如 -1 表示最后一个字节,-2 表示倒数第二个字节,以此类推。

不存在的 key 被当成是空字符串来处理,因此对一个不存在的 key 进行 BITCOUNT 操作,结果为 0 。

默认情况下,参数 startend 指定一个字节索引。我们可以使用一个附加参数 BIT 来指定一个比特索引。所以 0 是第一位,1 是第二位,以此类推。对于负值,-1 是最后一位,-2 是倒数第二位,依此类推。

返回值

返回一个整数,表示被设置为 1 的位的数量。

示例 1

对一个不存在的 key 进行 BITCOUNT 操作:

1
2
3
4
redis> EXISTS bits_key
(integer) 0
redis> BITCOUNT bits_key
(integer) 0

示例 2

对存储值的类型不是字符串的 key 进行 BITCOUNT 操作:

1
2
3
4
5
redis> TYPE list_key
list
redis> BITCOUNT list_key
(error) WRONGTYPE Operation against a key holding the wrong kind of value
redis>

示例 3

对存储值为字符串 JOHNSON LINkey 进行 BITCOUNT 操作:

1
2
3
4
5
redis> GET user
"JOHNSON LIN"
redis>
redis> BITCOUNT user
(integer) 38

字符串 JOHNSON LIN 的二进制表示:

1
01001010 01001111 01001000 01001110 01010011 01001111 01001110 00100000 01001100 01001001 01001110

示例 4

通过指定 startend 参数,只对存储值的第一个字节、第二个字节进行计数:

1
2
redis> BITCOUNT user 0 1 BYTE
(integer) 8

该命令也可以省略 BYTE 参数:

1
2
redis> BITCOUNT user 0 1
(integer) 8

示例 5

通过指定 startend 参数,只对存储值的倒数第一个字节、倒数第二个字节进行计数:

1
2
redis> BITCOUNT user -2 -1 BYTE
(integer) 7

省略 BYTE 参数:

1
2
redis> BITCOUNT user -2 -1
(integer) 7

示例 6

通过指定 startend 参数,只对存储值的第一位、第二位进行计数:

1
2
redis> BITCOUNT user 0 1 BIT
(integer) 1

示例 7

通过指定 startend 参数,只对存储值的倒数第八位至倒数第一位进行计数:

1
2
redis> BITCOUNT user -8 -1 BIT
(integer) 4

模式:使用 Bitmap 存储实时指标

对某些特定类型的数据,使用 Bitmap 存储可以非常节省空间。例如,记录 Web 应用用户访问历史以筛选参与新功能测试的用户。

使用 SETBIT 命令可以轻松实现这一功能。可以使用一个小的递增整数来代表每一天,如 0 代表应用上线首日,1 代表第二日,以此类推。

每次用户浏览页面时,应用可使用 SETBIT 命令设置与当前日期对应的位,记录用户在当日访问。之后只需对 Bitmap 调用 BITCOUNT 命令就可以轻松知道用户访问的总天数。

一篇名为“Fast easy realtime metrics using Redis bitmaps”的文章描述了类似方式,只是使用用户 ID 而非日期。

性能考虑因素

在上述计算用户访问天数的示例中,即使应用运行 10 年,每个用户的数据也仅为 365*10 比特,即每个用户只需 456 字节。对这样的数据量,BITCOUNT 的速度仍然与其他 $O(1)$ Redis 命令一样快,如 GETINCR

当位图很大时,有两种选择:

  1. 将一个大的 Bitmap 分散到不同的 key 中,作为小的 Bitmap 来处理。使用 Lua 脚本可以高效且原子地实现这一功能。

  2. 使用 BITCOUNTstartend 参数,遍历位图,每次只对部分位进行计算,然后在客户端累加结果。我们可以选择将累加结果缓存至一个键。

历史记录

从 Redis 7.0.0 版本开始:增加了 BYTEBIT 选项。

(END)