Redis命令: list任务队列、set集合运算与zset分值排序
纲要
本文覆盖 Redis 剩下三类数据结构的命令:list 的顺序性与队列能力、set 的去重与集合运算、sorted set 的分值排序机制。
list命令LPUSH头部插入、LRANGE范围查询、RPOP尾部弹出LLEN长度、BRPOP阻塞式弹出- 左进右出实现
FIFO任务队列
set命令SADD/SMEMBERS/SCARD- 集合运算:
SINTER交集、SUNION并集、SDIFF差集(顺序敏感) SREM移除
sorted set命令ZADD带score添加、ZRANGE范围查询(可带WITHSCORES)ZINCRBY增加分值、ZREM移除
list 类型命令
Redis 的 list 是简单的字符串列表 ,每个元素都是 string 类型,按插入顺序排序。
| 命令 | 语法 | 作用 |
|---|---|---|
LPUSH |
LPUSH key value1 [value2 ...] |
将一个或多个值插入列表头部(左侧) |
LRANGE |
LRANGE key start stop |
获取指定范围内的元素 |
RPOP |
RPOP key |
移除并获取列表最后一个元素(右侧) |
LLEN |
LLEN key |
获取列表长度 |
BRPOP |
BRPOP key timeout |
移除并获取最后一个元素,列表为空时阻塞 |
LPUSH 从左侧(头部)插入
list
d c b a
RPOP 从右侧(尾部)弹出
LPUSH 插入
bash
127.0.0.1:6379> LPUSH mylist a b c
(integer) 3
127.0.0.1:6379> LRANGE mylist 0 -1
1) "c"
2) "b"
3) "a"
注意顺序是 c b a 而非 a b c:a 最先插入位于队尾,c 最后插入位于队头。
再插入一个元素:
bash
127.0.0.1:6379> LPUSH mylist itcast
(integer) 4
127.0.0.1:6379> LRANGE mylist 0 -1
1) "itcast"
2) "c"
3) "b"
4) "a"
itcast 最后插入,因此位于最头部(最左侧)。
允许重复元素
bash
127.0.0.1:6379> LPUSH mylist a
(integer) 5
127.0.0.1:6379> LRANGE mylist 0 -1
1) "a"
2) "itcast"
3) "c"
4) "b"
5) "a"
列表中出现两个 a,说明 list 允许重复元素 ------这是它与 set 的关键差异。
LRANGE 范围说明
| 参数 | 含义 |
|---|---|
start |
起始下标,0 表示第一个元素 |
stop |
结束下标,-1 表示最后一个元素 |
LRANGE key 0 -1 表示查询全部元素,是最常用的写法。
RPOP 弹出
bash
127.0.0.1:6379> RPOP mylist
"a"
127.0.0.1:6379> LRANGE mylist 0 -1
1) "a"
2) "itcast"
3) "c"
4) "b"
RPOP 返回队尾元素 a,同时把它从列表中移除 ,列表由 5 个元素变为 4 个,新的队尾变成 b。
LLEN 长度
bash
127.0.0.1:6379> LLEN mylist
(integer) 4
BRPOP 阻塞式弹出
BRPOP 中的 B 即 Blocking。功能与 RPOP 相同,但当列表为空时会阻塞等待。
否
是
有
无,超过 timeout
BRPOP mylist 10
列表是否为空
立即弹出队尾元素
命令结束
阻塞等待
等待期间有新元素
超时自动结束
返回 nil
bash
# 列表中有元素:立即返回
127.0.0.1:6379> BRPOP mylist 10
1) "mylist"
2) "c"
# 列表已空:阻塞 10 秒后自动结束
127.0.0.1:6379> BRPOP mylist 10
(nil)
(10.05s)
返回值为一个二元数组:第一个元素是列表名,第二个是被弹出的元素值。末尾的 (10.05s) 显示阻塞耗时。
BRPOP是任务队列的关键命令:消费者无需轮询,列表为空时挂起等待,有新任务立即被唤醒处理,既实时又节省 CPU。
set 类型命令
set 是 string 类型的无序集合 ,成员唯一,不允许重复。
| 命令 | 语法 | 作用 |
|---|---|---|
SADD |
SADD key member1 [member2 ...] |
添加一个或多个成员 |
SMEMBERS |
SMEMBERS key |
返回所有成员 |
SCARD |
SCARD key |
返回成员数量 |
SINTER |
SINTER key1 key2 |
交集:两集合共有元素 |
SUNION |
SUNION key1 key2 |
并集:合并并去重 |
SDIFF |
SDIFF key1 key2 |
差集 :属于 key1 但不属于 key2 的元素 |
SREM |
SREM key member1 [member2 ...] |
移除一个或多个成员 |
SADD 与无序性
bash
127.0.0.1:6379> SADD myset a b c d
(integer) 4
127.0.0.1:6379> SMEMBERS myset
1) "a"
2) "d"
3) "c"
4) "b"
返回顺序为 a d c b,与插入顺序 a b c d 不一致,印证了 set 的无序性。
自动去重
bash
127.0.0.1:6379> SADD myset a b
(integer) 0
127.0.0.1:6379> SMEMBERS myset
1) "a"
2) "d"
3) "c"
4) "b"
重复添加 a、b 后,集合仍是 4 个元素,返回值 0 表示没有新成员被加入。
SCARD 成员数
bash
127.0.0.1:6379> SCARD myset
(integer) 4
集合运算
先准备第二个集合:
bash
127.0.0.1:6379> SADD myset2 a b x y
(integer) 4
此时:myset = {a, b, c, d},myset2 = {a, b, x, y}。
集合
myset = {a b c d}
myset2 = {a b x y}
SINTER 交集
{a b}
SUNION 并集
{a b c d x y}
SDIFF myset myset2
{c d}
bash
# 交集:两集合共有元素
127.0.0.1:6379> SINTER myset myset2
1) "a"
2) "b"
# 并集:合并后去重
127.0.0.1:6379> SUNION myset myset2
1) "a"
2) "b"
3) "c"
4) "d"
5) "x"
6) "y"
# 差集:myset 减 myset2
127.0.0.1:6379> SDIFF myset myset2
1) "c"
2) "d"
# 差集:myset2 减 myset(顺序调换,结果不同)
127.0.0.1:6379> SDIFF myset2 myset
1) "x"
2) "y"
| 运算 | 顺序是否影响结果 | 结果 |
|---|---|---|
SINTER 交集 |
否 | 共有元素 |
SUNION 并集 |
否 | 合并去重 |
SDIFF 差集 |
是 | 只从第一个集合中取,取其中不在后续集合中的元素 |
SDIFF的顺序敏感性 :SDIFF A B的数学含义是A - B。结果元素必定来自第一个集合A,且是A中不属于B的部分。调换顺序得到完全不同的结果,这是集合运算中最易出错的一点。
SREM 移除
bash
127.0.0.1:6379> SREM myset a b c
(integer) 3
127.0.0.1:6379> SMEMBERS myset
1) "d"
一次性移除三个成员,集合中只剩 d。
sorted set 类型命令
sorted set(zset)也是 string 类型元素的集合且不允许重复,但与 set 的本质区别是:每个元素关联一个 double 类型的 score(分值),集合按 score 从小到大排序。
list 排序依据
插入先后顺序
有序
sorted set 排序依据
score 分值大小
| 命令 | 语法 | 作用 |
|---|---|---|
ZADD |
ZADD key score1 member1 [score2 member2 ...] |
添加一个或多个带分值的元素 |
ZRANGE |
ZRANGE key start stop [WITHSCORES] |
按分值升序返回指定范围元素 |
ZINCRBY |
ZINCRBY key increment member |
为指定成员的 score 加上增量 |
ZREM |
ZREM key member [member ...] |
移除一个或多个成员 |
ZADD 与 ZRANGE
bash
127.0.0.1:6379> ZADD myset3 10.0 a 9.0 b
(integer) 2
127.0.0.1:6379> ZRANGE myset3 0 -1
1) "b"
2) "a"
a 的分值是 10.0,b 是 9.0,输出 b a------按分值从小到大排序,与插入顺序无关。
再插入一个介于两者之间的元素:
bash
127.0.0.1:6379> ZADD myset3 9.9 c
(integer) 1
127.0.0.1:6379> ZRANGE myset3 0 -1
1) "b"
2) "c"
3) "a"
c 的分值 9.9 介于 9.0 与 10.0 之间,因此排在中间,验证了排序依据确实是 score。
WITHSCORES 带分值返回
bash
127.0.0.1:6379> ZRANGE myset3 0 -1 WITHSCORES
1) "b"
2) "9"
3) "c"
4) "9.9000000000000004"
5) "a"
6) "10"
加 WITHSCORES 参数后,元素与分值交替 返回。注意 9.9 显示为 9.9000000000000004,这是 double 浮点数的精度表现,属于正常现象。
ZINCRBY 增加分值
bash
127.0.0.1:6379> ZINCRBY myset3 20 b
"29"
127.0.0.1:6379> ZRANGE myset3 0 -1 WITHSCORES
1) "c"
2) "9.9000000000000004"
3) "a"
4) "10"
5) "b"
6) "29"
b 的分值由 9 增加 20 变为 29,成为最大值,因此从首位排到了末位。参数顺序为:增量在前,成员在后。
ZREM 移除
bash
127.0.0.1:6379> ZREM myset3 b
(integer) 1
127.0.0.1:6379> ZRANGE myset3 0 -1
1) "c"
2) "a"
三类有序/去重结构对比
| 维度 | list |
set |
sorted set |
|---|---|---|---|
| 是否有序 | 是 | 否 | 是 |
| 排序依据 | 插入顺序 | --- | score 分值 |
| 是否允许重复 | 允许 | 不允许 | 不允许 |
| 核心命令前缀 | L |
S |
Z |
| 典型场景 | 任务队列 | 去重、集合运算 | 排行榜 |
完整可运行代码
java
package com.itheima.test;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.*;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* 演示 Redis list、set、sorted set 三类命令在 Java 中的对应操作。
*/
@SpringBootTest
public class RedisListSetZSetCommandTest {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* list:左进右出实现 FIFO 队列,以及阻塞式弹出。
*/
@Test
public void testList() {
ListOperations<String, Object> ops = redisTemplate.opsForList();
// LPUSH 从左侧插入,最终顺序为 c b a
ops.leftPushAll("mylist", "a", "b", "c");
System.out.println("LRANGE -> " + ops.range("mylist", 0, -1));
// 允许重复元素
ops.leftPush("mylist", "a");
System.out.println("允许重复 -> " + ops.range("mylist", 0, -1));
// LLEN
System.out.println("LLEN -> " + ops.size("mylist"));
// RPOP 从右侧弹出
System.out.println("RPOP -> " + ops.rightPop("mylist"));
System.out.println("弹出后 -> " + ops.range("mylist", 0, -1));
// BRPOP 阻塞式弹出,超时 10 秒
Object blocked = ops.rightPop("mylist", 10, TimeUnit.SECONDS);
System.out.println("BRPOP -> " + blocked);
}
/**
* set:去重特性与集合运算(注意 SDIFF 的顺序敏感性)。
*/
@Test
public void testSet() {
SetOperations<String, Object> ops = redisTemplate.opsForSet();
ops.add("myset", "a", "b", "c", "d");
// 重复添加,成员数不变
ops.add("myset", "a", "b");
System.out.println("SMEMBERS -> " + ops.members("myset"));
System.out.println("SCARD -> " + ops.size("myset"));
ops.add("myset2", "a", "b", "x", "y");
System.out.println("SINTER 交集 -> " + ops.intersect("myset", "myset2"));
System.out.println("SUNION 并集 -> " + ops.union("myset", "myset2"));
System.out.println("SDIFF myset-myset2 -> " + ops.difference("myset", "myset2"));
System.out.println("SDIFF myset2-myset -> " + ops.difference("myset2", "myset"));
ops.remove("myset", "a", "b", "c");
System.out.println("SREM 后 -> " + ops.members("myset"));
}
/**
* sorted set:按 score 排序,支持分值增减。
*/
@Test
public void testZSet() {
ZSetOperations<String, Object> ops = redisTemplate.opsForZSet();
ops.add("myset3", "a", 10.0);
ops.add("myset3", "b", 9.0);
ops.add("myset3", "c", 9.9);
System.out.println("ZRANGE(升序)-> " + ops.range("myset3", 0, -1));
Set<ZSetOperations.TypedTuple<Object>> withScores =
ops.rangeWithScores("myset3", 0, -1);
if (withScores != null) {
withScores.forEach(t -> System.out.println(" 成员 " + t.getValue() + " 分值 " + t.getScore()));
}
// ZINCRBY:b 的分值加 20
ops.incrementScore("myset3", "b", 20);
System.out.println("加分后 -> " + ops.rangeWithScores("myset3", 0, -1));
ops.remove("myset3", "b");
System.out.println("ZREM 后 -> " + ops.range("myset3", 0, -1));
}
/**
* 清理本文产生的测试数据。
*/
@Test
public void cleanUp() {
redisTemplate.delete("mylist");
redisTemplate.delete("myset");
redisTemplate.delete("myset2");
redisTemplate.delete("myset3");
System.out.println("清理完成,剩余 key -> " + redisTemplate.keys("*"));
}
}
工程目录结构
txt
springdataredis_demo/
├── pom.xml
└── src/
├── main/
│ ├── java/com/itheima/
│ │ ├── App.java
│ │ └── config/
│ │ └── RedisConfig.java
│ └── resources/
│ └── application.yml
└── test/
└── java/com/itheima/test/
└── RedisListSetZSetCommandTest.java
API 速览
Redis 命令 |
RedisTemplate 对应方法 |
说明 |
|---|---|---|
LPUSH key v... |
opsForList().leftPushAll(k, v...) |
左侧批量插入 |
LRANGE key 0 -1 |
opsForList().range(k, 0, -1) |
范围查询 |
RPOP key |
opsForList().rightPop(k) |
右侧弹出 |
LLEN key |
opsForList().size(k) |
列表长度 |
BRPOP key timeout |
opsForList().rightPop(k, timeout, unit) |
阻塞式弹出 |
SADD key m... |
opsForSet().add(k, m...) |
添加成员 |
SMEMBERS key |
opsForSet().members(k) |
所有成员 |
SCARD key |
opsForSet().size(k) |
成员数 |
SINTER k1 k2 |
opsForSet().intersect(k1, k2) |
交集 |
SUNION k1 k2 |
opsForSet().union(k1, k2) |
并集 |
SDIFF k1 k2 |
opsForSet().difference(k1, k2) |
差集,顺序敏感 |
SREM key m... |
opsForSet().remove(k, m...) |
移除成员 |
ZADD key s m |
opsForZSet().add(k, m, score) |
带分值添加 |
ZRANGE key 0 -1 |
opsForZSet().range(k, 0, -1) |
升序查询 |
ZRANGE ... WITHSCORES |
opsForZSet().rangeWithScores(k, 0, -1) |
带分值查询 |
ZINCRBY key inc m |
opsForZSet().incrementScore(k, m, inc) |
增加分值 |
ZREM key m |
opsForZSet().remove(k, m) |
移除成员 |
官方文档
- List 命令参考:https://redis.io/commands/?group=list
- Set 命令参考:https://redis.io/commands/?group=set
- Sorted Set 命令参考:https://redis.io/commands/?group=sorted-set
- Spring Data Redis 文档:https://docs.spring.io/spring-data/redis/docs/current/reference/html/
总结
本文三类命令各自围绕一个核心机制展开:list 的核心是顺序 + 允许重复 ,LPUSH 与 RPOP 分别从左右端操作,构成 FIFO 队列,而 BRPOP 的阻塞特性让消费者免于轮询;set 的核心是无序 + 唯一 ,真正的价值在 SINTER/SUNION/SDIFF 三种集合运算,其中 SDIFF 结果只取自第一个集合,参数顺序直接决定结果;sorted set 的核心是每个元素绑定一个 double 型 score 并按其升序排列 ,ZINCRBY 修改分值会实时改变排名,这是排行榜类需求的直接支撑。