Redis 位图
提示
来自deepseek解释
原文链接:https://redis.io/docs/latest/develop/data-types/strings/bitmaps/
代码示例图例
下面的代码示例展示了如何在不同编程语言和客户端库中执行相同的操作:
- Redis CLI:Redis 命令行界面
- C#(同步):StackExchange.Redis 同步客户端
- C#(异步):StackExchange.Redis 异步客户端
- Go:go-redis 客户端
- Java(同步 - Jedis):Jedis 同步客户端
- Java(异步 - Lettuce):Lettuce 异步客户端
- Java(响应式 - Lettuce):Lettuce 响应式/流式客户端
- JavaScript(Node.js):node-redis 客户端
- PHP:Predis 客户端
- Python:redis-py 客户端
- Rust(同步):redis-rs 同步客户端
- Rust(异步):redis-rs 异步客户端
每个代码示例都演示了不同语言中相同的基本操作。具体语法和模式因语言和客户端库而异,但底层的 Redis 命令和行为保持一致。
位图/位域命令摘要
本组共 7 条命令:
| 命令 | 摘要 | 复杂度 | 起始版本 |
|---|---|---|---|
| BITCOUNT | 计算字符串中被置位(人口计数)的数量。 | O(N) | 2.6.0 |
| BITFIELD | 对字符串执行任意的位域整数操作。 | 每个指定的子命令为 O(1) | 3.2.0 |
| BITFIELD_RO | 对字符串执行任意的只读位域整数操作。 | 每个指定的子命令为 O(1) | 6.0.0 |
| BITOP | 对多个字符串执行按位操作,并存储结果。 | O(N) | 2.6.0 |
| BITPOS | 查找字符串中第一个被置位(1)或清零(0)的位。 | O(N) | 2.8.7 |
| GETBIT | 按偏移量返回一个位的值。 | O(1) | 2.2.0 |
| SETBIT | 设置或清除字符串值在偏移量处的位。如果键不存在则创建它。 | O(1) | 2.2.0 |
位图并非实际的数据类型,而是一组面向位的操作,定义在字符串类型上,该类型被视为位向量。由于字符串是二进制安全的 blob,且最大长度为 512 MB,因此它们适合设置多达 2^32 个不同的位。
您可以在一个或多个字符串上执行按位操作。位图的一些用例如下:
- 在集合成员对应于整数 0-N 的情况下,高效的集合表示。
- 对象权限,其中每个位代表一个特定权限,类似于文件系统存储权限的方式。
示例
假设有 1000 名自行车手在乡村骑行,他们的自行车上贴有 0-999 标签的传感器。您想快速确定某个传感器是否在一小时内向跟踪服务器发送了 ping 以检查骑手的状态。
您可以使用位图来表示此场景,其中键引用当前小时。
- 骑手 123 在 2024 年 1 月 1 日 00:00 小时内向服务器发送了 ping。然后您可以确认骑手 123 已向服务器发送 ping。您还可以检查骑手 456 是否在同一小时内发送了 ping。
基础:使用 SETBIT 和 GETBIT 设置和获取单个位,用于跟踪二进制状态
命令: SETBIT, GETBIT
复杂度:
- SETBIT: O(1)
- GETBIT: O(1)
可用客户端: Redis CLI, C#, Go, Java (异步 - Lettuce), Java (响应式 - Lettuce), Java (同步 - Jedis), JavaScript (Node.js), PHP, Python
Redis CLI
> SETBIT pings:2024-01-01-00:00 123 1
(integer) 0
> GETBIT pings:2024-01-01-00:00 123
1
> GETBIT pings:2024-01-01-00:00 456
0C#
bool res1 = db.StringSetBit("pings:2024-01-01-00:00", 123, true);
Console.WriteLine(res1); // >>> 0
bool res2 = db.StringGetBit("pings:2024-01-01-00:00", 123);
Console.WriteLine(res2); // >>> True
bool res3 = db.StringGetBit("pings:2024-01-01-00:00", 456);
Console.WriteLine(res3); // >>> FalseGo
res1, err := rdb.SetBit(ctx, "pings:2024-01-01-00:00", 123, 1).Result()
if err != nil {
panic(err)
}
fmt.Println(res1) // >>> 0
res2, err := rdb.GetBit(ctx, "pings:2024-01-01-00:00", 123).Result()
if err != nil {
panic(err)
}
fmt.Println(res2) // >>> 1
res3, err := rdb.GetBit(ctx, "pings:2024-01-01-00:00", 456).Result()
if err != nil {
panic(err)
}
fmt.Println(res3) // >>> 0Java(异步 - Lettuce)
CompletableFuture<Void> ping = async.setbit("pings:2024-01-01-00:00", 123, 1).thenCompose(res1 -> {
System.out.println(res1); // >>> 0
return async.getbit("pings:2024-01-01-00:00", 123);
}).thenCompose(res2 -> {
System.out.println(res2); // >>> 1
return async.getbit("pings:2024-01-01-00:00", 456);
}).thenAccept(res3 -> {
System.out.println(res3); // >>> 0
}).toCompletableFuture();Java(响应式 - Lettuce)
Mono<Void> ping = reactive.setbit("pings:2024-01-01-00:00", 123, 1).doOnNext(res1 -> {
System.out.println(res1); // >>> 0
}).flatMap(v -> reactive.getbit("pings:2024-01-01-00:00", 123)).doOnNext(res2 -> {
System.out.println(res2); // >>> 1
}).flatMap(v -> reactive.getbit("pings:2024-01-01-00:00", 456)).doOnNext(res3 -> {
System.out.println(res3); // >>> 0
}).then();Java(同步 - Jedis)
boolean res1 = jedis.setbit("pings:2024-01-01-00:00", 123, true);
System.out.println(res1); // >>> false
boolean res2 = jedis.getbit("pings:2024-01-01-00:00", 123);
System.out.println(res2); // >>> true
boolean res3 = jedis.getbit("pings:2024-01-01-00:00", 456);
System.out.println(res3); // >>> falseJavaScript(Node.js)
const res1 = await client.setBit("pings:2024-01-01-00:00", 123, 1)
console.log(res1) // >>> 0
const res2 = await client.getBit("pings:2024-01-01-00:00", 123)
console.log(res2) // >>> 1
const res3 = await client.getBit("pings:2024-01-01-00:00", 456)
console.log(res3) // >>> 0PHP
$res1 = $r->setbit('pings:2024-01-01-00:00', 123, 1);
echo $res1 . PHP_EOL;
// >>> 0
$res2 = $r->getbit('pings:2024-01-01-00:00', 123);
echo $res2 . PHP_EOL;
// >>> 1
$res3 = $r->getbit('pings:2024-01-01-00:00', 456);
echo $res3 . PHP_EOL;
// >>> 0Python
res1 = r.setbit("pings:2024-01-01-00:00", 123, 1)
print(res1) # >>> 0
res2 = r.getbit("pings:2024-01-01-00:00", 123)
print(res2) # >>> 1
res3 = r.getbit("pings:2024-01-01-00:00", 456)
print(res3) # >>> 0位操作
位操作分为两组:常数时间的单比特操作,例如将位设置为 1 或 0,或获取其值;以及对位组的操作,例如计算给定范围内的置位数量(即人口计数)。
位图的最大优势之一是它们在存储信息时通常能极大节省空间。例如,在一个以递增用户 ID 表示不同用户的系统中,仅需 512 MB 内存就可以记录 40 亿用户的单比特信息(例如,了解用户是否愿意接收新闻通讯)。
SETBIT 命令接受位号作为第一个参数,以及要设置的位值(1 或 0)作为第二个参数。如果寻址的位超出当前字符串长度,该命令会自动扩展字符串。
GETBIT 仅返回指定索引处的位值。超出范围的位(寻址超出目标键中存储的字符串长度的位)始终被视为 0。
有三个命令用于操作位组:
BITOP在不同的字符串之间执行按位操作。提供的操作符有AND、OR、XOR、NOT、DIFF、DIFF1、ANDOR和ONE。BITCOUNT执行人口计数,报告置位为 1 的位数。BITPOS查找第一个具有指定值 0 或 1 的位。
BITPOS 和 BITCOUNT 都可以在字符串的字节范围上操作,而不是在整个字符串长度上运行。我们可以轻松地查看位图中被置位的位数。
位计数:当需要获取人口计数时,使用 BITCOUNT 统计位图中置位的数量
构建基础: ping
命令: BITCOUNT
复杂度:
- BITCOUNT: O(N)
可用客户端: Redis CLI, C#, Go, Java (异步 - Lettuce), Java (响应式 - Lettuce), Java (同步 - Jedis), JavaScript (Node.js), PHP, Python
Redis CLI
> BITCOUNT pings:2024-01-01-00:00
(integer) 1C#
bool res4 = db.StringSetBit("pings:2024-01-01-00:00", 123, true);
long res5 = db.StringBitCount("pings:2024-01-01-00:00");
Console.WriteLine(res5); // >>> 1Go
res4, err := rdb.BitCount(ctx, "pings:2024-01-01-00:00",
&redis.BitCount{
Start: 0,
End: 456,
}).Result()
if err != nil {
panic(err)
}
fmt.Println(res4) // >>> 1Java(异步 - Lettuce)
CompletableFuture<Void> bitcount = async.bitcount("pings:2024-01-01-00:00").thenAccept(res4 -> {
System.out.println(res4); // >>> 1
}).toCompletableFuture();Java(响应式 - Lettuce)
Mono<Void> bitcount = reactive.bitcount("pings:2024-01-01-00:00").doOnNext(res4 -> {
System.out.println(res4); // >>> 1
}).then();Java(同步 - Jedis)
long res4 = jedis.bitcount("pings:2024-01-01-00:00");
System.out.println(res4); // >>> 1JavaScript(Node.js)
await client.setBit("pings:2024-01-01-00:00", 123, 1)
const res4 = await client.bitCount("pings:2024-01-01-00:00")
console.log(res4) // >>> 1PHP
// 确保设置位
$r->setbit('pings:2024-01-01-00:00', 123, 1);
$res4 = $r->bitcount('pings:2024-01-01-00:00');
echo $res4 . PHP_EOL;
// >>> 1Python
r.setbit("pings:2024-01-01-00:00", 123, 1)
res4 = r.bitcount("pings:2024-01-01-00:00")
print(res4) # >>> 1例如,假设您想知道网站用户的最长连续访问天数。您从零开始计算天数,即您发布网站的那天,每当用户访问网站时使用 SETBIT 设置一个位。作为位索引,您只需取当前 Unix 时间,减去初始偏移量,然后除以每天秒数(通常为 3600*24)。
这样,每个用户就有一个包含每日访问信息的小字符串。通过 BITCOUNT 可以轻松获取给定用户访问网站的天数,而通过几次 BITPOS 调用,或简单地在客户端获取并分析位图,可以轻松计算最长连续访问天数。
按位操作
BITOP 命令在两个或多个源键上执行按位操作,并将结果存储在目标键中。
下面的示例展示了使用三个键的可操作操作:A(位模式 11011000)、B(00011001)和 C(01101100)。
从左到右编号位,从零开始,以下 SETBIT 命令将创建这些位图:
按位操作准备:使用 SETBIT 创建多个位图,为演示按位操作做准备
难度: 中级
构建基础: ping
命令: SETBIT, GET
复杂度:
- SETBIT: O(1)
- GET: O(1)
可用客户端: Redis CLI, C#, Go, Java (异步 - Lettuce), Java (响应式 - Lettuce), Java (同步 - Jedis), JavaScript (Node.js), PHP, Python
Redis CLI
> SETBIT A 0 1
(integer) 0
> SETBIT A 1 1
(integer) 0
> SETBIT A 3 1
(integer) 0
> SETBIT A 4 1
(integer) 0
> GET A
"\xd8"
# 十六进制值: 0xd8 = 0b11011000
> SETBIT B 3 1
(integer) 0
> SETBIT B 4 1
(integer) 0
> SETBIT B 7 1
(integer) 0
> GET B
"\x19"
# 十六进制值: 0x19 = 0b00011001
> SETBIT C 1 1
(integer) 0
> SETBIT C 2 1
(integer) 0
> SETBIT C 4 1
(integer) 0
> SETBIT C 5 1
(integer) 0
> GET C
"l"
# ASCII "l" = 十六进制 0x6c = 0b01101100C#
// NRedisStack 不支持按位操作。Go
rdb.SetBit(ctx, "A", 0, 1)
rdb.SetBit(ctx, "A", 1, 1)
rdb.SetBit(ctx, "A", 3, 1)
rdb.SetBit(ctx, "A", 4, 1)
ba, _ := rdb.Get(ctx, "A").Bytes()
fmt.Printf("%08b\n", ba[0])
// >>> 11011000
rdb.SetBit(ctx, "B", 3, 1)
rdb.SetBit(ctx, "B", 4, 1)
rdb.SetBit(ctx, "B", 7, 1)
bb, _ := rdb.Get(ctx, "B").Bytes()
fmt.Printf("%08b\n", bb[0])
// >>> 00011001
rdb.SetBit(ctx, "C", 1, 1)
rdb.SetBit(ctx, "C", 2, 1)
rdb.SetBit(ctx, "C", 4, 1)
rdb.SetBit(ctx, "C", 5, 1)
bc, _ := rdb.Get(ctx, "C").Bytes()
fmt.Printf("%08b\n", bc[0])
// >>> 01101100Java(异步 - Lettuce)
CompletableFuture<Void> setup = async.setbit("A", 0, 1).thenCompose(v -> async.setbit("A", 1, 1))
.thenCompose(v -> async.setbit("A", 3, 1)).thenCompose(v -> async.setbit("A", 4, 1))
.thenCompose(v -> async.setbit("B", 3, 1)).thenCompose(v -> async.setbit("B", 4, 1))
.thenCompose(v -> async.setbit("B", 7, 1)).thenCompose(v -> async.setbit("C", 1, 1))
.thenCompose(v -> async.setbit("C", 2, 1)).thenCompose(v -> async.setbit("C", 4, 1))
.thenCompose(v -> async.setbit("C", 5, 1)).thenCompose(v -> asyncBytes.get("A".getBytes()))
.thenApply(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bitsA = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bitsA); // >>> 11011000
return bitsA;
}).thenCompose(v -> asyncBytes.get("B".getBytes())).thenApply(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bitsB = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bitsB); // >>> 00011001
return bitsB;
})
// 打印 C
.thenCompose(v -> asyncBytes.get("C".getBytes())).thenAccept(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bitsC = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bitsC); // >>> 01101100
}).toCompletableFuture();Java(响应式 - Lettuce)
Mono<Void> setup = reactive.setbit("A", 0, 1).then(reactive.setbit("A", 1, 1)).then(reactive.setbit("A", 3, 1))
.then(reactive.setbit("A", 4, 1)).then(reactive.setbit("B", 3, 1)).then(reactive.setbit("B", 4, 1))
.then(reactive.setbit("B", 7, 1)).then(reactive.setbit("C", 1, 1)).then(reactive.setbit("C", 2, 1))
.then(reactive.setbit("C", 4, 1)).then(reactive.setbit("C", 5, 1)).then(reactiveBytes.get("A".getBytes()))
.doOnNext(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bitsA = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bitsA); // >>> 11011000
}).then(reactiveBytes.get("B".getBytes())).doOnNext(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bitsB = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bitsB); // >>> 00011001
}).then(reactiveBytes.get("C".getBytes())).doOnNext(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bitsC = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bitsC); // >>> 01101100
}).then();Java(同步 - Jedis)
jedis.setbit("A", 0, true);
jedis.setbit("A", 1, true);
jedis.setbit("A", 3, true);
jedis.setbit("A", 4, true);
byte[] res5 = jedis.get("A".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res5[0] & 0xFF)).replace(' ', '0'));
// >>> 11011000
jedis.setbit("B", 3, true);
jedis.setbit("B", 4, true);
jedis.setbit("B", 7, true);
byte[] res6 = jedis.get("B".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res6[0] & 0xFF)).replace(' ', '0'));
// >>> 00011001
jedis.setbit("C", 1, true);
jedis.setbit("C", 2, true);
jedis.setbit("C", 4, true);
jedis.setbit("C", 5, true);
byte[] res7 = jedis.get("C".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res7[0] & 0xFF)).replace(' ', '0'));
// >>> 01101100JavaScript(Node.js)
await client.setBit("A", 0, 1)
await client.setBit("A", 1, 1)
await client.setBit("A", 3, 1)
await client.setBit("A", 4, 1)
const res5 = await client.get("A")
console.log(res5.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 11011000
await client.setBit("B", 3, 1)
await client.setBit("B", 4, 1)
await client.setBit("B", 7, 1)
const res6 = await client.get("B")
console.log(res6.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 00011001
await client.setBit("C", 1, 1)
await client.setBit("C", 2, 1)
await client.setBit("C", 4, 1)
await client.setBit("C", 5, 1)
const res7 = await client.get("C")
console.log(res7.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 01101100PHP
$r->setbit('A', 0, 1);
$r->setbit('A', 1, 1);
$r->setbit('A', 3, 1);
$r->setbit('A', 4, 1);
$res5 = $r->get('A');
echo str_pad(decbin(ord($res5)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
// >>> 11011000
$r->setbit('B', 3, 1);
$r->setbit('B', 4, 1);
$r->setbit('B', 7, 1);
$res6 = $r->get('B');
echo str_pad(decbin(ord($res6)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
// >>> 00011001
$r->setbit('C', 1, 1);
$r->setbit('C', 2, 1);
$r->setbit('C', 4, 1);
$r->setbit('C', 5, 1);
$res7 = $r->get('C');
echo str_pad(decbin(ord($res7)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
// >>> 01101100Python
r.setbit("A", 0, 1)
r.setbit("A", 1, 1)
r.setbit("A", 3, 1)
r.setbit("A", 4, 1)
res5 = r.get("A")
print("{:08b}".format(int.from_bytes(res5, "big")))
# >>> 11011000
r.setbit("B", 3, 1)
r.setbit("B", 4, 1)
r.setbit("B", 7, 1)
res6 = r.get("B")
print("{:08b}".format(int.from_bytes(res6, "big")))
# >>> 00011001
r.setbit("C", 1, 1)
r.setbit("C", 2, 1)
r.setbit("C", 4, 1)
r.setbit("C", 5, 1)
res7 = r.get("C")
print("{:08b}".format(int.from_bytes(res7, "big")))
# >>> 01101100AND
仅当在所有源键中都置位时,才在目标键中将位设置为 1。
AND 操作:当需要查找多个集合中的公共位时,使用 BITOP AND 查找所有源位图中都置位的位
难度: 中级
构建基础: bitop_setup
命令: BITOP, GET
复杂度:
- BITOP: O(N)
- GET: O(1)
可用客户端: Redis CLI, C#, Go, Java (异步 - Lettuce), Java (响应式 - Lettuce), Java (同步 - Jedis), JavaScript (Node.js), PHP, Python
Redis CLI
> BITOP AND R A B C
(integer) 1
> GET R
"\b"
# ASCII "\b" (退格) = 十六进制 0x08 = 0b00001000C#
// NRedisStack 不支持按位操作。Go
rdb.BitOpAnd(ctx, "R", "A", "B", "C")
br, _ := rdb.Get(ctx, "R").Bytes()
fmt.Printf("%08b\n", br[0])
// >>> 00001000Java(异步 - Lettuce)
CompletableFuture<Void> andOp = async.bitopAnd("R", "A", "B", "C")
.thenCompose(len -> asyncBytes.get("R".getBytes())).thenAccept(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bits); // >>> 00001000
}).toCompletableFuture();Java(响应式 - Lettuce)
Mono<Void> andOp = reactive.bitopAnd("R", "A", "B", "C").then(reactiveBytes.get("R".getBytes())).doOnNext(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bits); // >>> 00001000
}).then();Java(同步 - Jedis)
jedis.bitop(BitOP.AND, "R", "A", "B", "C");
byte[] res8 = jedis.get("R".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res8[0] & 0xFF)).replace(' ', '0'));
// >>> 00001000JavaScript(Node.js)
await client.bitOp("AND", "R", ["A", "B", "C"])
const res8 = await client.get("R")
console.log(res8.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 00001000PHP
$r->bitop('AND', 'R', 'A', 'B', 'C');
$res8 = $r->get('R');
echo str_pad(decbin(ord($res8)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
// >>> 00001000Python
r.bitop("AND", "R", "A", "B", "C")
res8 = r.get("R")
print("{:08b}".format(int.from_bytes(res8, "big")))
# >>> 00001000OR
如果至少一个源键中置位,则在目标键中将位设置为 1。
OR 操作:当需要合并多个集合时,使用 BITOP OR 查找至少一个源位图中置位的位
难度: 中级
构建基础: bitop_setup
命令: BITOP, GET
复杂度:
- BITOP: O(N)
- GET: O(1)
可用客户端: Redis CLI, C#, Go, Java (异步 - Lettuce), Java (响应式 - Lettuce), Java (同步 - Jedis), JavaScript (Node.js), PHP, Python
Redis CLI
> BITOP OR R A B C
(integer) 1
> GET R
"\xfd"
# 十六进制值: 0xfd = 0b11111101C#
// NRedisStack 不支持按位操作。Go
rdb.BitOpOr(ctx, "R", "A", "B", "C")
br, _ = rdb.Get(ctx, "R").Bytes()
fmt.Printf("%08b\n", br[0])
// >>> 11111101Java(异步 - Lettuce)
CompletableFuture<Void> orOp = async.bitopOr("R", "A", "B", "C").thenCompose(len -> asyncBytes.get("R".getBytes()))
.thenAccept(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bits); // >>> 11111101
}).toCompletableFuture();Java(响应式 - Lettuce)
Mono<Void> orOp = reactive.bitopOr("R", "A", "B", "C").then(reactiveBytes.get("R".getBytes())).doOnNext(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bits); // >>> 11111101
}).then();Java(同步 - Jedis)
jedis.bitop(BitOP.OR, "R", "A", "B", "C");
byte[] res9 = jedis.get("R".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res9[0] & 0xFF)).replace(' ', '0'));
// >>> 11111101JavaScript(Node.js)
await client.bitOp("OR", "R", ["A", "B", "C"])
const res9 = await client.get("R")
console.log(res9.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 11111101PHP
$r->bitop('OR', 'R', 'A', 'B', 'C');
$res9 = $r->get('R');
echo str_pad(decbin(ord($res9)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
// >>> 11111101Python
r.bitop("OR", "R", "A", "B", "C")
res9 = r.get("R")
print("{:08b}".format(int.from_bytes(res9, "big")))
# >>> 11111101XOR
对于两个源键,如果两个键中的位值不同,则在目标键中将位设置为 1。对于三个或更多源键,前两个键的 XOR 结果再与下一个键进行 XOR,依此类推。
XOR 操作:当需要识别差异时,使用 BITOP XOR 查找位图之间不同的位
难度: 中级
构建基础: bitop_setup
命令: BITOP, GET
复杂度:
- BITOP: O(N)
- GET: O(1)
可用客户端: Redis CLI, C#, Go, Java (异步 - Lettuce), Java (响应式 - Lettuce), Java (同步 - Jedis), JavaScript (Node.js), PHP, Python
Redis CLI
> BITOP XOR R A B
(integer) 1
> GET R
"\xc1"
# 十六进制值: 0xc1 = 0b11000001C#
// NRedisStack 不支持按位操作。Go
rdb.BitOpXor(ctx, "R", "A", "B")
br, _ = rdb.Get(ctx, "R").Bytes()
fmt.Printf("%08b\n", br[0])
// >>> 11000001Java(异步 - Lettuce)
CompletableFuture<Void> xorOp = async.bitopXor("R", "A", "B").thenCompose(len -> asyncBytes.get("R".getBytes()))
.thenAccept(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bits); // >>> 11000001
}).toCompletableFuture();Java(响应式 - Lettuce)
Mono<Void> xorOp = reactive.bitopXor("R", "A", "B").then(reactiveBytes.get("R".getBytes())).doOnNext(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bits); // >>> 11000001
}).then();Java(同步 - Jedis)
jedis.bitop(BitOP.XOR, "R", "A", "B");
byte[] res10 = jedis.get("R".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res10[0] & 0xFF)).replace(' ', '0'));
// >>> 11000001JavaScript(Node.js)
await client.bitOp("XOR", "R", ["A", "B"]) // XOR 这里使用两个键
const res10 = await client.get("R")
console.log(res10.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 11000001PHP
$r->bitop('XOR', 'R', 'A', 'B');
$res10 = $r->get('R');
echo str_pad(decbin(ord($res10)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
// >>> 11000001Python
r.bitop("XOR", "R", "A", "B")
res10 = r.get("R")
print("{:08b}".format(int.from_bytes(res10, "big")))
# >>> 11000001NOT
如果源键中未置位,则在目标键中将位设置为 1(这是唯一的一元运算符)。
NOT 操作:当需要对集合取反时,使用 BITOP NOT 反转位图中的所有位
难度: 中级
构建基础: bitop_setup
命令: BITOP, GET
复杂度:
- BITOP: O(N)
- GET: O(1)
可用客户端: Redis CLI, C#, Go, Java (异步 - Lettuce), Java (响应式 - Lettuce), Java (同步 - Jedis), JavaScript (Node.js), PHP, Python
Redis CLI
> BITOP NOT R A
(integer) 1
> GET R
"'"
# ASCII "'" (单引号) = 十六进制 0x27 = 0b00100111C#
// NRedisStack 不支持按位操作。Go
rdb.BitOpNot(ctx, "R", "A")
br, _ = rdb.Get(ctx, "R").Bytes()
fmt.Printf("%08b\n", br[0])
// >>> 00100111Java(异步 - Lettuce)
CompletableFuture<Void> notOp = async.bitopNot("R", "A").thenCompose(len -> asyncBytes.get("R".getBytes()))
.thenAccept(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bits); // >>> 00100111
}).toCompletableFuture();Java(响应式 - Lettuce)
Mono<Void> notOp = reactive.bitopNot("R", "A").then(reactiveBytes.get("R".getBytes())).doOnNext(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bits); // >>> 00100111
}).then();Java(同步 - Jedis)
jedis.bitop(BitOP.NOT, "R", "A");
byte[] res11 = jedis.get("R".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res11[0] & 0xFF)).replace(' ', '0'));
// >>> 00100111JavaScript(Node.js)
await client.bitOp("NOT", "R", "A")
const res11 = await client.get("R")
console.log(res11.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 00100111PHP
$r->bitop('NOT', 'R', 'A');
$res11 = $r->get('R');
echo str_pad(decbin(ord($res11)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
// >>> 00100111Python
r.bitop("NOT", "R", "A")
res11 = r.get("R")
print("{:08b}".format(int.from_bytes(res11, "big")))
# >>> 00100111DIFF
如果在第一个源键中置位,但不在任何其他源键中置位,则在目标键中将位设置为 1。
DIFF 操作:当需要集合差集时,使用 BITOP DIFF 查找第一个位图中置位但其他位图中未置位的位
难度: 高级
构建基础: bitop_setup
命令: BITOP, GET
复杂度:
- BITOP: O(N)
- GET: O(1)
可用客户端: Redis CLI, C#, Go, Java (异步 - Lettuce), Java (响应式 - Lettuce), Java (同步 - Jedis), JavaScript (Node.js), PHP, Python
Redis CLI
> BITOP DIFF R A B C
(integer) 1
> GET R
"\x80"
# 十六进制值: 0x80 = 0b10000000C#
// NRedisStack 不支持按位操作。Go
rdb.BitOpDiff(ctx, "R", "A", "B", "C")
br, _ = rdb.Get(ctx, "R").Bytes()
fmt.Printf("%08b\n", br[0])
// >>> 10000000Java(异步 - Lettuce)
CompletableFuture<Void> diffOp = async.bitopDiff("R", "A", "B", "C")
.thenCompose(len -> asyncBytes.get("R".getBytes())).thenAccept(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bits); // >>> 10000000
}).toCompletableFuture();Java(响应式 - Lettuce)
Mono<Void> diffOp = reactive.bitopDiff("R", "A", "B", "C").then(reactiveBytes.get("R".getBytes())).doOnNext(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bits); // >>> 10000000
}).then();Java(同步 - Jedis)
jedis.bitop(BitOP.DIFF, "R", "A", "B", "C");
byte[] res12 = jedis.get("R".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res12[0] & 0xFF)).replace(' ', '0'));
// >>> 10000000JavaScript(Node.js)
await client.bitOp("DIFF", "R", ["A", "B", "C"])
const res12 = await client.get("R")
console.log(res12.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 10000000PHP
$r->bitop('DIFF', 'R', 'A', 'B', 'C');
$res12 = $r->get('R');
echo str_pad(decbin(ord($res12)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
// >>> 10000000Python
r.bitop("DIFF", "R", "A", "B", "C")
res12 = r.get("R")
print("{:08b}".format(int.from_bytes(res12, "big")))
# >>> 10000000DIFF1
如果在第一个源键中未置位,但在至少一个其他源键中置位,则在目标键中将位设置为 1。
DIFF1 操作:当需要反向差集时,使用 BITOP DIFF1 查找第一个位图中未置位但至少一个其他位图中置位的位
难度: 高级
构建基础: bitop_setup
命令: BITOP, GET
复杂度:
- BITOP: O(N)
- GET: O(1)
可用客户端: Redis CLI, C#, Go, Java (异步 - Lettuce), Java (响应式 - Lettuce), Java (同步 - Jedis), JavaScript (Node.js), PHP, Python
Redis CLI
> BITOP DIFF1 R A B C
(integer) 1
> GET R
"%"
# ASCII "%" (百分号) = 十六进制 0x25 = 0b00100101C#
// NRedisStack 不支持按位操作。Go
rdb.BitOpDiff1(ctx, "R", "A", "B", "C")
br, _ = rdb.Get(ctx, "R").Bytes()
fmt.Printf("%08b\n", br[0])
// >>> 00100101Java(异步 - Lettuce)
CompletableFuture<Void> diff1Op = async.bitopDiff1("R", "A", "B", "C")
.thenCompose(len -> asyncBytes.get("R".getBytes())).thenAccept(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bits); // >>> 00100101
}).toCompletableFuture();Java(响应式 - Lettuce)
Mono<Void> diff1Op = reactive.bitopDiff1("R", "A", "B", "C").then(reactiveBytes.get("R".getBytes()))
.doOnNext(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bits); // >>> 00100101
}).then();Java(同步 - Jedis)
jedis.bitop(BitOP.DIFF1, "R", "A", "B", "C");
byte[] res13 = jedis.get("R".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res13[0] & 0xFF)).replace(' ', '0'));
// >>> 00100101JavaScript(Node.js)
await client.bitOp("DIFF1", "R", ["A", "B", "C"])
const res13 = await client.get("R")
console.log(res13.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 00100101PHP
$r->bitop('DIFF1', 'R', 'A', 'B', 'C');
$res13 = $r->get('R');
echo str_pad(decbin(ord($res13)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
// >>> 00100101Python
r.bitop("DIFF1", "R", "A", "B", "C")
res13 = r.get("R")
print("{:08b}".format(int.from_bytes(res13, "big")))
# >>> 00100101ANDOR
如果在第一个源键中置位,并且也在至少一个其他源键中置位,则在目标键中将位设置为 1。
ANDOR 操作:当需要第一个位图与至少一个其他位图的交集时,使用 BITOP ANDOR
难度: 高级
构建基础: bitop_setup
命令: BITOP, GET
复杂度:
- BITOP: O(N)
- GET: O(1)
可用客户端: Redis CLI, C#, Go, Java (异步 - Lettuce), Java (响应式 - Lettuce), Java (同步 - Jedis), JavaScript (Node.js), PHP, Python
Redis CLI
> BITOP ANDOR R A B C
(integer) 1
> GET R
"X"
# ASCII "X" = 十六进制 0x58 = 0b01011000C#
// NRedisStack 不支持按位操作。Go
rdb.BitOpAndOr(ctx, "R", "A", "B", "C")
br, _ = rdb.Get(ctx, "R").Bytes()
fmt.Printf("%08b\n", br[0])
// >>> 01011000Java(异步 - Lettuce)
CompletableFuture<Void> andorOp = async.bitopAndor("R", "A", "B", "C")
.thenCompose(len -> asyncBytes.get("R".getBytes())).thenAccept(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bits); // >>> 01011000
}).toCompletableFuture();Java(响应式 - Lettuce)
Mono<Void> andorOp = reactive.bitopAndor("R", "A", "B", "C").then(reactiveBytes.get("R".getBytes()))
.doOnNext(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bits); // >>> 01011000
}).then();Java(同步 - Jedis)
jedis.bitop(BitOP.ANDOR, "R", "A", "B", "C");
byte[] res14 = jedis.get("R".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res14[0] & 0xFF)).replace(' ', '0'));
// >>> 01011000JavaScript(Node.js)
await client.bitOp("ANDOR", "R", ["A", "B", "C"])
const res14 = await client.get("R")
console.log(res14.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 01011000PHP
$r->bitop('ANDOR', 'R', 'A', 'B', 'C');
$res14 = $r->get('R');
echo str_pad(decbin(ord($res14)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
// >>> 01011000Python
r.bitop("ANDOR", "R", "A", "B", "C")
res14 = r.get("R")
print("{:08b}".format(int.from_bytes(res14, "big")))
# >>> 01011000ONE
如果恰好在一个源键中置位,则在目标键中将位设置为 1。
ONE 操作:当需要排他性成员关系时,使用 BITOP ONE 查找恰好在一个位图中置位的位
难度: 高级
构建基础: bitop_setup
命令: BITOP, GET
复杂度:
- BITOP: O(N)
- GET: O(1)
可用客户端: Redis CLI, C#, Go, Java (异步 - Lettuce), Java (响应式 - Lettuce), Java (同步 - Jedis), JavaScript (Node.js), PHP, Python
Redis CLI
> BITOP ONE R A B C
(integer) 1
> GET R
"\xa5"
# 十六进制值: 0xa5 = 0b10100101C#
// NRedisStack 不支持按位操作。Go
rdb.BitOpOne(ctx, "R", "A", "B", "C")
br, _ = rdb.Get(ctx, "R").Bytes()
fmt.Printf("%08b\n", br[0])
// >>> 10100101Java(异步 - Lettuce)
CompletableFuture<Void> oneOp = async.bitopOne("R", "A", "B", "C")
.thenCompose(len -> asyncBytes.get("R".getBytes())).thenAccept(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bits); // >>> 10100101
}).toCompletableFuture();Java(响应式 - Lettuce)
Mono<Void> oneOp = reactive.bitopOne("R", "A", "B", "C").then(reactiveBytes.get("R".getBytes())).doOnNext(res -> {
byte b = (res != null && res.length > 0) ? res[0] : 0;
String bits = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0');
System.out.println(bits); // >>> 10100101
}).then();Java(同步 - Jedis)
jedis.bitop(BitOP.ONE, "R", "A", "B", "C");
byte[] res15 = jedis.get("R".getBytes());
System.out.println(String.format("%8s", Integer.toBinaryString(res15[0] & 0xFF)).replace(' ', '0'));
// >>> 10100101JavaScript(Node.js)
await client.bitOp("ONE", "R", ["A", "B", "C"])
const res15 = await client.get("R")
console.log(res15.readUInt8(0).toString(2).padStart(8, '0'))
// >>> 10100101PHP
$r->bitop('ONE', 'R', 'A', 'B', 'C');
$res15 = $r->get('R');
echo str_pad(decbin(ord($res15)), 8, '0', STR_PAD_LEFT) . PHP_EOL;
// >>> 10100101Python
r.bitop("ONE", "R", "A", "B", "C")
res15 = r.get("R")
print("{:08b}".format(int.from_bytes(res15, "big")))
# >>> 10100101将位图拆分为多个键
位图很容易拆分为多个键,例如为了分片数据集,以及通常最好避免使用超大键。要将位图拆分到不同的键中,而不是将所有位设置在一个键中,一个简单的策略是每个键存储 M 位,并使用 bit-number/M 作为键名,使用 bit-number MOD M 作为键内寻址的第 N 位。
性能
SETBIT 和 GETBIT 为 O(1)。BITOP 为 O(n),其中 n 是比较中最长字符串的长度。
了解更多
- Redis Bitmaps Explained 教您如何在在线游戏中使用位图进行地图探索。
- Redis University 的 RU101 详细介绍了 Redis 位图。