BITCOUNT key [start end [BYTE|BIT]]
从 2.6.0 开始可用。
时间复杂度: O(N)
计算字符串中设置的位数(人口计数)。
默认情况下,检查字符串中包含的所有字节。可以仅在传递附加参数start和end的间隔中指定计数操作。
与GETRANGE命令一样,start 和 end 可以包含负值,以便从字符串末尾开始索引字节,其中 -1 是最后一个字节,-2 是倒数第二个字节,依此类推。
不存在的键被视为空字符串,因此该命令将返回零。
默认情况下,附加参数start和end指定字节索引。我们可以使用附加参数BIT来指定位索引。所以 0 是第一位,1 是第二位,依此类推。对于负值,-1 是最后一位,-2 是倒数第二位,依此类推。
返回值
整数回复
设置为 1 的位数。
例子
redis> SET mykey "foobar"
"OK"
redis> BITCOUNT mykey
(integer) 26
redis> BITCOUNT mykey 0 0
(integer) 4
redis> BITCOUNT mykey 1 1
(integer) 6
redis> BITCOUNT mykey 1 1 BYTE
(integer) 6
redis> BITCOUNT mykey 5 30 BIT
(integer) 17
redis>
模式:使用位图的实时指标
位图是某种信息的一种非常节省空间的表示。一个示例是需要用户访问历史记录的 Web 应用程序,以便确定哪些用户是 beta 功能的良好目标。
使用SETBIT命令很容易完成,用一个小的渐进整数标识每一天。例如,第 0 天是应用程序上线的第一天,第二天是第 1 天,以此类推。
每次用户执行页面查看时,应用程序可以使用SETBIT命令设置与当天相对应的位来记录用户在当天访问网站的情况。
稍后,只需针对位图调用BITCOUNT命令即可知道用户访问网站的单日天数。
在名为“使用 Redis 位图的快速简单实时指标”的文章中描述了使用用户 ID 而不是天数的类似模式。
性能注意事项
在上面的计算天数的例子中,即使在应用程序上线 10 年后,每个用户仍然只有365*10少量数据,即每个用户只有 456 个字节。有了这个数据量,BITCOUNT仍然与任何其他O(1) Redis 命令(如GET或INCR )一样快。
当位图很大时,有两种选择:
- 采用每次修改位图时递增的单独键。使用小型 Redis Lua 脚本可以非常高效和原子化。
- 使用BITCOUNT 开始和结束可选参数增量运行位图 ,在客户端累积结果,并可选择将结果缓存到键中。
历史
- Redis 版本 >= 7.0.0:添加了
BYTE|BIT
选项。
BITFIELD key [GET encoding offset] [SET encoding offset value] [INCRBY encoding offset increment] [OVERFLOW WRAP|SAT|FAIL]
自 3.2.0 起可用。
时间复杂度:每个指定的子命令 O(1)
该命令将 Redis 字符串视为位数组,并且能够寻址具有不同位宽和任意非(必要)对齐偏移量的特定整数字段。实际上,使用此命令可以将位偏移 1234 处的有符号 5 位整数设置为特定值,从偏移 4567 检索 31 位无符号整数。类似地,该命令处理指定整数的递增和递减,提供用户可以配置的保证和明确指定的上溢和下溢行为。
BITFIELD能够在同一个命令调用中使用多个位字段。它需要一个操作列表来执行,并返回一个回复数组,其中每个数组与参数列表中的相应操作匹配。
例如,以下命令在位偏移 100 处递增一个 5 位有符号整数,并在位偏移 0 处获取 4 位无符号整数的值:
> BITFIELD mykey INCRBY i5 100 1 GET u4 0
1) (integer) 1
2) (integer) 0
注意:
- GET使用当前字符串长度之外的位(包括根本不存在密钥的情况)进行寻址,会导致执行操作,就像丢失的部分全部由设置为 0 的位组成。
- SET使用当前字符串长度之外的或位寻址INCRBY将扩大字符串,根据最远位触摸,根据需要将其补零,以获得所需的最小长度。
支持的子命令和整数编码
以下是支持的命令列表。
- GET——
返回指定的位域。 - SET——
设置指定的位域并返回它的旧值。 - INCRBY
-- 递增或递减(如果给出负递增)指定位字段并返回新值。
还有另一个子命令,它只 通过设置溢出行为来改变连续调用INCRBY和子命令调用的行为:SET
- 溢出 [WRAP|SAT|FAIL]
在需要整数编码的地方,它可以通过在有i符号整数和u无符号整数前面加上我们的整数编码的位数来组成。例如u8,一个 8 位的无符号整数和i16一个 16 位的有符号整数。
有符号整数支持的编码最多为 64 位,无符号整数最多支持 63 位。无符号整数的这种限制是由于目前 Redis 协议无法返回 64 位无符号整数作为回复。
位和位置偏移
有两种方法可以在位域命令中指定偏移量。如果指定了一个没有任何前缀的数字,则它仅用作字符串内从零开始的位偏移量。
但是,如果偏移量以字符为前缀#,则指定的偏移量乘以整数编码的宽度,例如:
BITFIELD mystring SET i8 #0 100 SET i8 #1 200
将第一个 i8 整数设置在偏移量 0 处,第二个设置在偏移量 8 处。这样,如果您想要的是给定大小的整数的普通数组,那么您不必自己在客户端内部进行数学运算。
溢出控制
使用该OVERFLOW命令,用户可以通过指定以下行为之一来微调递增或递减溢出(或下溢)的行为:
- WRAP:环绕,包括有符号和无符号整数。在无符号整数的情况下,包装就像对整数可以包含的最大值进行模运算(C 标准行为)。使用有符号整数而不是换行意味着上溢向最负值重新开始,而下溢向最正值重新开始,因此例如,如果将i8整数设置为值 127,则将其增加 1 将产生-128。
- SAT:使用饱和算法,即在下溢时将值设置为最小整数值,在上溢时设置为最大整数值。例如,i8从值 120 开始以 10 的增量递增一个整数,将导致值 127,并且进一步的递增将始终保持该值为 127。下溢也会发生同样的情况,但在最负值处阻止该值.
- FAIL:在此模式下,不会对检测到的溢出或下溢执行任何操作。相应的返回值设置为 NULL 以向调用者发出条件信号。
请注意,每个OVERFLOW语句仅影响子命令列表中跟随它的INCRBY和SET 命令,直到下一条OVERFLOW 语句。
默认情况下,如果没有另外指定,则使用WRAP 。
> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 1
2) (integer) 1
> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 2
2) (integer) 2
> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 3
2) (integer) 3
> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 0
2) (integer) 3
返回值
该命令返回一个数组,其中每个条目是在同一位置给出的子命令的相应结果。OVERFLOW子命令不算作生成回复。
以下是OVERFLOW FAIL返回 NULL 的示例。
> BITFIELD mykey OVERFLOW FAIL incrby u2 102 1
1) (nil)
自 3.2.0 起可用。
时间复杂度:每个指定的子命令 O(1)
该命令将 Redis 字符串视为位数组,并且能够寻址具有不同位宽和任意非(必要)对齐偏移量的特定整数字段。实际上,使用此命令可以将位偏移 1234 处的有符号 5 位整数设置为特定值,从偏移 4567 检索 31 位无符号整数。类似地,该命令处理指定整数的递增和递减,提供用户可以配置的保证和明确指定的上溢和下溢行为。
BITFIELD能够在同一个命令调用中使用多个位字段。它需要一个操作列表来执行,并返回一个回复数组,其中每个数组与参数列表中的相应操作匹配。
例如,以下命令在位偏移 100 处递增一个 5 位有符号整数,并在位偏移 0 处获取 4 位无符号整数的值:
BITFIELD mykey INCRBY i5 100 1 GET u4 0
- (integer) 1
- (integer) 0
注意:
GET使用当前字符串长度之外的位(包括根本不存在密钥的情况)进行寻址,会导致执行操作,就像丢失的部分全部由设置为 0 的位组成。
SET使用当前字符串长度之外的或位寻址INCRBY将扩大字符串,根据最远位触摸,根据需要将其补零,以获得所需的最小长度。
支持的子命令和整数编码
以下是支持的命令列表。
GET——
SET——
INCRBY
还有另一个子命令,它只 通过设置溢出行为来改变连续调用INCRBY和子命令调用的行为:SET
溢出 [WRAP|SAT|FAIL]
在需要整数编码的地方,它可以通过在有i符号整数和u无符号整数前面加上我们的整数编码的位数来组成。例如u8,一个 8 位的无符号整数和i16一个 16 位的有符号整数。
有符号整数支持的编码最多为 64 位,无符号整数最多支持 63 位。无符号整数的这种限制是由于目前 Redis 协议无法返回 64 位无符号整数作为回复。
位和位置偏移
有两种方法可以在位域命令中指定偏移量。如果指定了一个没有任何前缀的数字,则它仅用作字符串内从零开始的位偏移量。
但是,如果偏移量以字符为前缀#,则指定的偏移量乘以整数编码的宽度,例如:
BITFIELD mystring SET i8 #0 100 SET i8 #1 200
将第一个 i8 整数设置在偏移量 0 处,第二个设置在偏移量 8 处。这样,如果您想要的是给定大小的整数的普通数组,那么您不必自己在客户端内部进行数学运算。
溢出控制
使用该OVERFLOW命令,用户可以通过指定以下行为之一来微调递增或递减溢出(或下溢)的行为:
WRAP:环绕,包括有符号和无符号整数。在无符号整数的情况下,包装就像对整数可以包含的最大值进行模运算(C 标准行为)。使用有符号整数而不是换行意味着上溢向最负值重新开始,而下溢向最正值重新开始,因此例如,如果将i8整数设置为值 127,则将其增加 1 将产生-128。
SAT:使用饱和算法,即在下溢时将值设置为最小整数值,在上溢时设置为最大整数值。例如,i8从值 120 开始以 10 的增量递增一个整数,将导致值 127,并且进一步的递增将始终保持该值为 127。下溢也会发生同样的情况,但在最负值处阻止该值.
FAIL:在此模式下,不会对检测到的溢出或下溢执行任何操作。相应的返回值设置为 NULL 以向调用者发出条件信号。
请注意,每个OVERFLOW语句仅影响子命令列表中跟随它的INCRBY和SET 命令,直到下一条OVERFLOW 语句。
默认情况下,如果没有另外指定,则使用WRAP 。
BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
- (integer) 1
- (integer) 1
BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
- (integer) 2
- (integer) 2
BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
- (integer) 3
- (integer) 3
BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
- (integer) 0
- (integer) 3
返回值
该命令返回一个数组,其中每个条目是在同一位置给出的子命令的相应结果。OVERFLOW子命令不算作生成回复。
以下是OVERFLOW FAIL返回 NULL 的示例。
BITFIELD mykey OVERFLOW FAIL incrby u2 102 1
- (nil)
动机
此命令的动机是能够将许多小整数存储为单个大位图(或分割为几个键以避免具有大键)的内存效率极高,并为 Redis 应用开辟了新的用例,尤其是在实时分析领域。以受控方式指定溢出的能力支持此用例。
有趣的事实:Reddit 的 2017 年愚人节项目r/place是使用 Redis BITFIELD 命令构建的,以便获取协作画布的内存表示。
性能注意事项
通常BITFIELD是一个快速命令,但是请注意,寻址当前短字符串的远位将触发分配,这可能比在已经存在的位上执行命令成本更高。
位的顺序
BITFIELD使用的表示将位图视为具有位号 0 是第一个字节的最高有效位,依此类推,因此例如在偏移量 7 处将 5 位无符号整数设置为值 23 到先前设置为的位图中全为零,将产生以下表示:
+--------+--------+
|00000001|01110000|
+--------+--------+
当偏移量和整数大小与字节边界对齐时,这与大端序相同,但是当这种对齐方式不存在时,了解字节内的位如何排序也很重要。
BITFIELD_RO key GET encoding offset
从 6.2.0 开始可用。
时间复杂度:每个指定的子命令 O(1)
BITFIELD命令的只读变体。它类似于原始的BITFIELD,但只接受GET子命令并且可以安全地用于只读副本。
由于原始BITFIELD具有SET和INCRBY选项,因此它在技术上被标记为 Redis 命令表中的写入命令。因此,即使连接处于只读模式,Redis 集群中的只读副本也会将其重定向到主实例(请参阅 Redis 集群的READONLY命令)。
从 Redis 6.2 开始,BITFIELD_RO引入该变体是为了允许只读副本中的BITFIELD行为,而不会破坏命令标志的兼容性。
有关更多详细信息,请参阅原始BITFIELD。
例子
BITFIELD_RO hello GET i8 16
返回值
数组回复:一个数组,每个条目都是在同一位置给出的子命令的相应结果。
BITOP operation destkey key [key ...]
从 2.6.0 开始可用。
时间复杂度: O(N)
在多个键(包含字符串值)之间执行按位运算并将结果存储在目标键中。
BITOP命令支持四种按位运算:AND、OR、XOR 和NOT,因此调用该命令的有效形式是:
- BITOP AND destkey srckey1 srckey2 srckey3 ... srckeyN
- BITOP OR destkey srckey1 srckey2 srckey3 ... srckeyN
- BITOP XOR destkey srckey1 srckey2 srckey3 ... srckeyN
- BITOP NOT destkey srckey
如您所见, NOT很特殊,因为它只需要一个输入键,因为它执行位反转,因此它仅作为一元运算符才有意义。
运算结果始终存储在destkey.
处理不同长度的字符串
当在具有不同长度的字符串之间执行操作时,所有比集合中最长字符串短的字符串都被视为被零填充到最长字符串的长度。
对于不存在的键也是如此,它们被认为是一个零字节的流,直到最长的字符串的长度。
返回值
整数回复
存储在目标键中的字符串的大小,即等于最长输入字符串的大小。
例子
redis> SET key1 "foobar"
"OK"
redis> SET key2 "abcdef"
"OK"
redis> BITOP AND dest key1 key2
(integer) 6
redis> GET dest
"`bc`ab"
redis>
模式:使用位图的实时指标
BITOP是对BITCOUNT命令文档中记录的模式的一个很好的补充。可以组合不同的位图以获得执行人口计数操作的目标位图。
有关有趣的用例,请参阅名为“使用 Redis 位图的快速简单实时指标”的文章。
性能注意事项
BITOP是一个潜在的慢命令,因为它在O(N)时间内运行。对长输入字符串运行它时应小心。
对于涉及大量输入的实时指标和统计数据,一个好的方法是使用副本(禁用只读选项),其中执行按位操作以避免阻塞主实例。
BITPOS key bit [start [end [BYTE|BIT]]]
自 2.8.7 起可用。
时间复杂度: O(N)
返回字符串中设置为 1 或 0 的第一位的位置。
返回位置,将字符串视为从左到右的位数组,其中第一个字节的最高有效位在位置 0,第二个字节的最高有效位在位置 8,依此类推。
GETBIT和SETBIT遵循相同的位位置约定。
默认情况下,检查字符串中包含的所有字节。可以仅在传递附加参数start和end的指定间隔中查找位(可以只传递start,该操作将假定 end 是字符串的最后一个字节。但是如解释的那样存在语义差异之后)。默认情况下,该范围被解释为一个字节范围而不是一个位范围,因此start=0andend=2意味着查看前三个字节。
您可以使用可选BIT修饰符指定范围应解释为位范围。所以start=0和end=2意味着查看前三位。
请注意,即使使用start和end指定范围,位位置也始终作为从位零开始的绝对值返回。
与GETRANGE命令一样,start 和 end 可以包含负值,以便从字符串末尾开始索引字节,其中 -1 是最后一个字节,-2 是倒数第二个字节,依此类推。指定时BIT,-1 是最后一位,-2 是倒数第二位,依此类推。
不存在的键被视为空字符串。
返回值
整数回复
该命令根据请求返回设置为 1 或 0 的第一位的位置。
如果我们查找设置位(位参数为 1)并且字符串为空或仅由零字节组成,则返回 -1。
如果我们寻找清除位(位参数为 0)并且字符串仅包含设置为 1 的位,则该函数返回第一个位而不是右侧字符串的一部分。因此,如果字符串是设置为该值0xff的三个字节,则命令BITPOS key 0将返回 24,因为直到第 23 位,所有位都是 1。
基本上,如果您查找清除位并且不指定范围或仅指定开始参数,该函数会将字符串的右侧视为用零填充。
但是,如果您正在寻找清除位并使用start和end指定范围,则此行为会发生变化。如果在指定范围内没有找到清除位,则函数返回 -1,因为用户指定了一个清除范围并且该范围内没有 0 位。
例子
redis> SET mykey "\xff\xf0\x00"
"OK"
redis> BITPOS mykey 0
(integer) 12
redis> SET mykey "\x00\xff\xf0"
"OK"
redis> BITPOS mykey 1 0
(integer) 8
redis> BITPOS mykey 1 2
(integer) 16
redis> BITPOS mykey 1 2 -1 BYTE
(integer) 16
redis> BITPOS mykey 1 7 15 BIT
(integer) 8
redis> set mykey "\x00\x00\x00"
"OK"
redis> BITPOS mykey 1
(integer) -1
redis> BITPOS mykey 1 7 -3 BIT
(integer) -1
redis>
历史
- Redis 版本 >= 7.0.0:添加了
BYTE|BIT
选项。
GETBIT key offset
从 2.2.0 开始可用。
时间复杂度: O(1)
返回存储在key的字符串值中offset处的位值。
当offset超过字符串长度时,字符串被假定为一个 0 位的连续空间。当key不存在时,它被假定为空字符串,因此偏移量总是超出范围,并且该值也被假定为具有 0 位的连续空间。
返回值
整数回复:存储在偏移处的位值。
例子
redis> SETBIT mykey 7 1
(integer) 0
redis> GETBIT mykey 0
(integer) 0
redis> GETBIT mykey 7
(integer) 1
redis> GETBIT mykey 100
(integer) 0
redis>
SETBIT key offset value
从 2.2.0 开始可用。
时间复杂度: O(1)
设置或清除存储在key的字符串值中offset的位。
该位根据value设置或清除, value可以是 0 或 1。
当key不存在时,创建一个新的字符串值。字符串被增长以确保它可以在offset保持一点。偏移量参数必须大于或等于 0,并且小于 2 32 (这将位图限制为 512MB)。当key处的字符串增长时,添加的位设置为 0。
警告:当设置最后一个可能的位(偏移量等于 2 32 -1)并且存储在key的字符串值还没有保存字符串值,或者保存一个小字符串值时,Redis 需要分配所有可以阻塞的中间内存服务器一段时间。在 2010 MacBook Pro 上,设置位数 2 32 -1(512MB 分配)需要 ~300ms,设置位数 2 30 -1(128MB 分配)需要 ~80ms,设置位数 2 28 -1(32MB 分配)需要 ~30ms并设置位号 2 26-1(8MB 分配)大约需要 8 毫秒。请注意,一旦完成第一次分配,随后对同一键的SETBIT调用将不会产生分配开销。
返回值
整数回复:存储在偏移处的原始位值。
例子
redis> SETBIT mykey 7 1
(integer) 0
redis> SETBIT mykey 7 0
(integer) 1
redis> GET mykey
"\u0000"
redis>
模式:访问整个位图
在某些情况下,您需要一次设置单个位图的所有位,例如将其初始化为默认的非零值时。可以通过多次调用SETBIT命令来执行此操作,每个需要设置的位一个。但是,作为一种优化,您可以使用单个SET命令来设置整个位图。
位图不是一种实际的数据类型,而是在字符串类型上定义的一组面向位的操作(有关更多信息,请参阅 数据类型介绍页面的位图部分)。这意味着位图可以与字符串命令一起使用,最重要的是与SET和 GET一起使用。
因为 Redis 的字符串是二进制安全的,所以位图被简单地编码为字节流。字符串的第一个字节对应于位图的偏移量 0..7,第二个字节对应于 8..15 范围,依此类推。
例如,设置几个位后,获取位图的字符串值将如下所示:
> SETBIT bitmapsarestrings 2 1
> SETBIT bitmapsarestrings 3 1
> SETBIT bitmapsarestrings 5 1
> SETBIT bitmapsarestrings 10 1
> SETBIT bitmapsarestrings 11 1
> SETBIT bitmapsarestrings 14 1
> GET bitmapsarestrings
"42"
通过获取位图的字符串表示,客户端可以通过在其本机编程语言中使用本机位操作提取位值来解析响应的字节。对称地,也可以通过在客户端执行位到字节的编码并 使用结果字符串调用SET来设置整个位图。
模式:设置多个位
SETBIT擅长设置单个位,当需要设置多个位时可以多次调用。要优化此操作,您可以使用对可变参数BITFIELD命令的单个调用和使用类型字段来替换多个SETBIT调用。u1
例如,上面的示例可以替换为:
> BITFIELD bitsinabitmap SET u1 2 1 SET u1 3 1 SET u1 5 1 SET u1 10 1 SET u1 11 1 SET u1 14 1
高级模式:访问位图范围
也可以使用GETRANGE和SETRANGE字符串命令来有效地访问位图中的一系列位偏移。以下是可以使用EVAL 命令运行的惯用 Redis Lua 脚本的示例实现:
--[[
Sets a bitmap range
Bitmaps are stored as Strings in Redis. A range spans one or more bytes,
so we can call [SETRANGE](/commands/setrange) when entire bytes need to be set instead of flipping
individual bits. Also, to avoid multiple internal memory allocations in
Redis, we traverse in reverse.
Expected input:
KEYS[1] - bitfield key
ARGV[1] - start offset (0-based, inclusive)
ARGV[2] - end offset (same, should be bigger than start, no error checking)
ARGV[3] - value (should be 0 or 1, no error checking)
]]--
-- A helper function to stringify a binary string to semi-binary format
local function tobits(str)
local r = ''
for i = 1, string.len(str) do
local c = string.byte(str, i)
local b = ' '
for j = 0, 7 do
b = tostring(bit.band(c, 1)) .. b
c = bit.rshift(c, 1)
end
r = r .. b
end
return r
end
-- Main
local k = KEYS[1]
local s, e, v = tonumber(ARGV[1]), tonumber(ARGV[2]), tonumber(ARGV[3])
-- First treat the dangling bits in the last byte
local ms, me = s % 8, (e + 1) % 8
if me > 0 then
local t = math.max(e - me + 1, s)
for i = e, t, -1 do
redis.call('SETBIT', k, i, v)
end
e = t
end
-- Then the danglings in the first byte
if ms > 0 then
local t = math.min(s - ms + 7, e)
for i = s, t, 1 do
redis.call('SETBIT', k, i, v)
end
s = t + 1
end
-- Set a range accordingly, if at all
local rs, re = s / 8, (e + 1) / 8
local rl = re - rs
if rl > 0 then
local b = '\255'
if 0 == v then
b = '\0'
end
redis.call('SETRANGE', k, rs, string.rep(b, rl))
end
**注意:**从位图中获取一系列位偏移的实现留给读者作为练习。