Redis 列表(下)

本课学习列表的查询、修改、插入等高级操作。

LINDEX:按索引获取元素

LINDEX根据索引获取列表中的元素,不删除元素。

基本用法

REDIS
LPUSH mylist "one" "two" "three" "four" "five"

# 获取第一个元素(索引0)
LINDEX mylist 0
"five"

# 获取第二个元素
LINDEX mylist 1
"four"

# 获取最后一个元素
LINDEX mylist -1
"one"

# 获取倒数第二个元素
LINDEX mylist -2
"two"

索引超出范围

REDIS
# 索引超出范围返回nil
LINDEX mylist 10
(nil)

LINDEX mylist -10
(nil)
💡 性能: LINDEX时间复杂度O(N),N是索引的绝对值。访问头部和尾部快,访问中间慢。

LSET:按索引设置元素

LSET修改指定索引位置的元素。

基本用法

REDIS
LRANGE mylist 0 -1
1) "five"
2) "four"
3) "three"
4) "two"
5) "one"

# 修改第一个元素
LSET mylist 0 "FIRST"
OK

# 修改最后一个元素
LSET mylist -1 "LAST"
OK

LRANGE mylist 0 -1
1) "FIRST"
2) "four"
3) "three"
4) "two"
5) "LAST"

索引超出范围

REDIS
LSET mylist 10 "value"
(error) ERR index out of range
⚠️ 注意: LSET只能修改已存在的索引,不能用来插入新元素。

LINSERT:在指定元素前后插入

LINSERT在指定元素的前面或后面插入新元素。

LINSERT BEFORE:在前面插入

REDIS
LRANGE mylist 0 -1
1) "FIRST"
2) "four"
3) "three"
4) "two"
5) "LAST"

# 在"three"前面插入"BEFORE_THREE"
LINSERT mylist BEFORE "three" "BEFORE_THREE"
(integer) 6  # 返回列表长度

LRANGE mylist 0 -1
1) "FIRST"
2) "four"
3) "BEFORE_THREE"
4) "three"
5) "two"
6) "LAST"

LINSERT AFTER:在后面插入

REDIS
# 在"three"后面插入"AFTER_THREE"
LINSERT mylist AFTER "three" "AFTER_THREE"
(integer) 7

LRANGE mylist 0 -1
1) "FIRST"
2) "four"
3) "BEFORE_THREE"
4) "three"
5) "AFTER_THREE"
6) "two"
7) "LAST"

元素不存在

REDIS
# 指定的pivot元素不存在
LINSERT mylist BEFORE "notexist" "value"
(integer) -1  # 返回-1表示未找到pivot
⚠️ 性能警告: LINSERT时间复杂度O(N),需要遍历列表查找pivot。列表很长时会很慢。

LREM:删除指定值的元素

LREM删除列表中等于指定值的元素。

基本用法

REDIS
LPUSH mylist "a" "b" "a" "c" "a" "d"

LRANGE mylist 0 -1
1) "d"
2) "a"
3) "c"
4) "a"
5) "b"
6) "a"

# 从头部开始删除2个"a"
LREM mylist 2 "a"
(integer) 2  # 删除了2个

LRANGE mylist 0 -1
1) "d"
2) "c"
3) "b"
4) "a"

参数说明

参数 说明
count > 0 从头部开始删除count个
count < 0 从尾部开始删除abs(count)个
count = 0 删除所有匹配的元素

从尾部删除

REDIS
LPUSH mylist "a" "b" "a" "c" "a"

# 从尾部开始删除1个"a"
LREM mylist -1 "a"
(integer) 1

删除所有匹配元素

REDIS
LPUSH mylist "a" "b" "a" "c" "a"

# 删除所有"a"
LREM mylist 0 "a"
(integer) 3

LRANGE mylist 0 -1
1) "c"
2) "b"

RPOPLPUSH:移动元素

RPOPLPUSH将一个列表的尾部元素弹出,并插入到另一个列表的头部。

基本用法

REDIS
# 源列表
LPUSH source "one" "two" "three"

# 目标列表
LPUSH destination "a" "b"

# 将source的尾部元素移动到destination的头部
RPOPLPUSH source destination
"one"  # 返回移动的元素

LRANGE source 0 -1
1) "three"
2) "two"

LRANGE destination 0 -1
1) "one"
2) "a"
3) "b"

应用:循环队列

REDIS
# 将任务从队列尾部移回队列头部,实现循环处理
RPOPLPUSH queue:task queue:task

应用:备份队列

REDIS
# 处理任务时,将任务移到备份队列
RPOPLPUSH queue:task queue:backup

# 如果处理失败,可以从备份队列恢复

BRPOPLPUSH:阻塞式移动

BRPOPLPUSH是RPOPLPUSH的阻塞版本。

REDIS
# 如果source列表为空,阻塞等待最多10秒
BRPOPLPUSH source destination 10
💡 用途: BRPOPLPUSH常用于可靠的消息队列,确保消息不丢失。

LMOVE:移动元素(Redis 6.2+)

LMOVE是RPOPLPUSH的通用版本,可以指定从哪里弹出和插入到哪里。

REDIS
# 从source的右侧弹出,插入到destination的左侧
LMOVE source destination RIGHT LEFT

# 从source的左侧弹出,插入到destination的右侧
LMOVE source destination LEFT RIGHT

列表高级应用

场景1:分页查询

REDIS
# 文章列表
LPUSH articles "article:1" "article:2" ... "article:100"

# 第1页(每页10条)
LRANGE articles 0 9

# 第2页
LRANGE articles 10 19

# 第3页
LRANGE articles 20 29

场景2:最近联系人

REDIS
# 添加联系人到最近列表
LPUSH contacts:user:1 "user:2"

# 如果已存在,先删除再添加(移到最前面)
LREM contacts:user:1 0 "user:2"
LPUSH contacts:user:1 "user:2"

# 获取最近10个联系人
LRANGE contacts:user:1 0 9

场景3:轮询调度

REDIS
# 任务列表
LPUSH tasks "task1" "task2" "task3"

# 轮询处理:取出任务,处理后放回队尾
RPOPLPUSH tasks tasks
"task1"  # 处理task1

# 下次轮询会处理task2

场景4:限制长度的日志

REDIS
# 添加日志
LPUSH log:app "log message"

# 保持最近1000条日志
LTRIM log:app 0 999

# 查看最近日志
LRANGE log:app 0 99

列表性能优化

1. 避免中间操作

REDIS
# ❌ 性能差:在列表中间插入或删除
LINSERT mylist BEFORE "middle" "new"
LREM mylist 1 "middle"

# ✅ 性能好:只在头尾操作
LPUSH mylist "new"
LPOP mylist

2. 避免大列表

REDIS
# ❌ 列表太大
LLEN mylist
(integer) 1000000

# ✅ 使用LTRIM限制长度
LTRIM mylist 0 9999

3. 使用LRANGE分批获取

REDIS
# ❌ 一次性获取大量元素
LRANGE mylist 0 -1

# ✅ 分批获取
LRANGE mylist 0 99
LRANGE mylist 100 199

4. 选择合适的数据结构

需求 推荐数据结构
队列/栈 List
最新N条 List + LTRIM
分页查询 List + LRANGE
去重列表 Set 或 Sorted Set
带权重排序 Sorted Set

列表限制

1. 查找元素慢

REDIS
# 查找特定值需要遍历整个列表
LINDEX mylist 500000  # O(N)操作,很慢

2. 中间操作慢

REDIS
# 在中间插入或删除需要移动元素
LINSERT mylist BEFORE "middle" "new"  # O(N)

3. 无法按值查找索引

Redis没有提供按值查找索引的命令,需要应用层自己实现。

❓ 常见问题

Q LINDEX和LRANGE的区别?
A LINDEX返回单个元素,LRANGE返回元素列表。LINDEX mylist 0 等价于 LRANGE mylist 0 0。
Q 如何在列表中间插入元素?
A 使用LINSERT命令,但性能较差。如果需要频繁中间操作,考虑使用其他数据结构。
Q RPOPLPUSH是原子操作吗?
A 是的。RPOPLPUSH是原子操作,要么全部成功,要么全部失败。
Q 如何实现优先级队列?
A 使用多个列表(如queue:high, queue:low),先检查高优先级队列,再检查低优先级队列。
Q 列表和集合的区别?
A 列表有序可重复,集合无序不重复。需要顺序用列表,需要去重用集合。

📖 小节

📝 作业

  1. 索引操作: 创建一个列表,使用LINDEX获取不同位置的元素,使用LSET修改元素
  2. 插入删除: 使用LINSERT在列表中间插入元素,使用LREM删除指定值的元素
  3. 元素移动: 使用RPOPLPUSH实现一个循环队列,模拟任务轮询
  4. 分页查询: 创建一个包含20个元素的列表,实现每页5条的分页查询

下一课

下一课我们将学习 Redis 集合(上),学习集合的基本操作。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏