<返回目录     Powered by claud/xia兄

第4课: 列表操作

LPUSH、RPUSH、LPOP、RPOP、LRANGE、LINDEX等

List类型深入解析

List是Redis中的有序字符串列表,按照插入顺序排序。它是一个双向链表结构,支持在头部和尾部进行高效的插入和删除操作。List最多可以包含2^32-1个元素(超过40亿个)。

List的特点

List的底层实现

Redis的List底层使用两种数据结构:

基本插入操作

示例1:LPUSH和RPUSH命令

# LPUSH: 从列表左侧(头部)插入元素
127.0.0.1:6379> LPUSH mylist "world"
(integer) 1
127.0.0.1:6379> LPUSH mylist "hello"
(integer) 2
# 结果: ["hello", "world"]

# RPUSH: 从列表右侧(尾部)插入元素
127.0.0.1:6379> RPUSH mylist "!"
(integer) 3
# 结果: ["hello", "world", "!"]

# 一次插入多个元素
127.0.0.1:6379> LPUSH numbers 3 2 1
(integer) 3
# 结果: [1, 2, 3](注意插入顺序)

127.0.0.1:6379> RPUSH letters a b c d
(integer) 4
# 结果: ["a", "b", "c", "d"]

# LPUSHX/RPUSHX: 仅当列表存在时才插入
127.0.0.1:6379> LPUSHX newlist "value"
(integer) 0  # 列表不存在,插入失败

127.0.0.1:6379> LPUSH newlist "first"
(integer) 1
127.0.0.1:6379> LPUSHX newlist "second"
(integer) 2  # 列表存在,插入成功

示例2:LINSERT命令

# LINSERT: 在指定元素前或后插入新元素
127.0.0.1:6379> RPUSH fruits "apple" "banana" "orange"
(integer) 3

# 在"banana"前插入"grape"
127.0.0.1:6379> LINSERT fruits BEFORE "banana" "grape"
(integer) 4
# 结果: ["apple", "grape", "banana", "orange"]

# 在"banana"后插入"mango"
127.0.0.1:6379> LINSERT fruits AFTER "banana" "mango"
(integer) 5
# 结果: ["apple", "grape", "banana", "mango", "orange"]

# 如果指定元素不存在,返回-1
127.0.0.1:6379> LINSERT fruits BEFORE "watermelon" "kiwi"
(integer) -1

基本删除操作

示例3:LPOP和RPOP命令

# LPOP: 从列表左侧(头部)弹出元素
127.0.0.1:6379> RPUSH queue "task1" "task2" "task3"
(integer) 3
127.0.0.1:6379> LPOP queue
"task1"
127.0.0.1:6379> LPOP queue
"task2"

# RPOP: 从列表右侧(尾部)弹出元素
127.0.0.1:6379> RPOP queue
"task3"

# 列表为空时返回nil
127.0.0.1:6379> LPOP queue
(nil)

# Redis 6.2+支持一次弹出多个元素
127.0.0.1:6379> RPUSH numbers 1 2 3 4 5
(integer) 5
127.0.0.1:6379> LPOP numbers 2
1) "1"
2) "2"
127.0.0.1:6379> RPOP numbers 2
1) "5"
2) "4"

示例4:LREM命令

# LREM: 删除指定数量的指定元素
# LREM key count value
# count > 0: 从头到尾删除count个value
# count < 0: 从尾到头删除|count|个value
# count = 0: 删除所有value

127.0.0.1:6379> RPUSH items "a" "b" "a" "c" "a" "d" "a"
(integer) 7

# 从头删除2个"a"
127.0.0.1:6379> LREM items 2 "a"
(integer) 2
# 结果: ["b", "c", "a", "d", "a"]

# 从尾删除1个"a"
127.0.0.1:6379> LREM items -1 "a"
(integer) 1
# 结果: ["b", "c", "a", "d"]

# 删除所有"a"
127.0.0.1:6379> RPUSH items "a" "a"
(integer) 6
127.0.0.1:6379> LREM items 0 "a"
(integer) 3
# 结果: ["b", "c", "d"]

示例5:LTRIM命令

# LTRIM: 保留指定范围的元素,删除其他元素
127.0.0.1:6379> RPUSH logs "log1" "log2" "log3" "log4" "log5" "log6"
(integer) 6

# 保留索引1到3的元素
127.0.0.1:6379> LTRIM logs 1 3
OK
127.0.0.1:6379> LRANGE logs 0 -1
1) "log2"
2) "log3"
3) "log4"

# 保留最新的3条日志
127.0.0.1:6379> RPUSH logs "log5" "log6" "log7"
(integer) 6
127.0.0.1:6379> LTRIM logs -3 -1
OK
127.0.0.1:6379> LRANGE logs 0 -1
1) "log5"
2) "log6"
3) "log7"

查询操作

示例6:LRANGE命令

# LRANGE: 获取指定范围的元素
127.0.0.1:6379> RPUSH numbers 1 2 3 4 5 6 7 8 9 10
(integer) 10

# 获取前3个元素
127.0.0.1:6379> LRANGE numbers 0 2
1) "1"
2) "2"
3) "3"

# 获取所有元素
127.0.0.1:6379> LRANGE numbers 0 -1
1) "1"
2) "2"
...
10) "10"

# 获取最后3个元素
127.0.0.1:6379> LRANGE numbers -3 -1
1) "8"
2) "9"
3) "10"

# 分页查询(每页5条)
# 第1页
127.0.0.1:6379> LRANGE numbers 0 4
# 第2页
127.0.0.1:6379> LRANGE numbers 5 9

示例7:LINDEX和LLEN命令

# LINDEX: 获取指定索引的元素
127.0.0.1:6379> RPUSH colors "red" "green" "blue" "yellow"
(integer) 4

127.0.0.1:6379> LINDEX colors 0
"red"
127.0.0.1:6379> LINDEX colors 2
"blue"
127.0.0.1:6379> LINDEX colors -1
"yellow"  # 最后一个元素
127.0.0.1:6379> LINDEX colors -2
"blue"  # 倒数第二个元素

# 索引超出范围返回nil
127.0.0.1:6379> LINDEX colors 10
(nil)

# LLEN: 获取列表长度
127.0.0.1:6379> LLEN colors
(integer) 4

# 列表不存在时返回0
127.0.0.1:6379> LLEN nonexist
(integer) 0

示例8:LSET命令

# LSET: 设置指定索引的元素值
127.0.0.1:6379> RPUSH animals "cat" "dog" "bird"
(integer) 3

127.0.0.1:6379> LSET animals 1 "rabbit"
OK
127.0.0.1:6379> LRANGE animals 0 -1
1) "cat"
2) "rabbit"
3) "bird"

# 使用负数索引
127.0.0.1:6379> LSET animals -1 "fish"
OK
127.0.0.1:6379> LRANGE animals 0 -1
1) "cat"
2) "rabbit"
3) "fish"

# 索引超出范围会报错
127.0.0.1:6379> LSET animals 10 "value"
(error) ERR index out of range

阻塞操作

示例9:BLPOP和BRPOP命令

# BLPOP/BRPOP: 阻塞式弹出元素
# 如果列表为空,会阻塞等待,直到有元素或超时

# 终端1: 阻塞等待(超时时间10秒)
127.0.0.1:6379> BLPOP queue 10
# 阻塞中...

# 终端2: 插入元素
127.0.0.1:6379> RPUSH queue "task1"
(integer) 1

# 终端1: 立即返回
1) "queue"  # 键名
2) "task1"  # 弹出的元素

# 超时返回nil
127.0.0.1:6379> BLPOP emptyqueue 5
(nil)
(5.01s)

# 可以同时监听多个列表
127.0.0.1:6379> BLPOP queue1 queue2 queue3 10
# 哪个列表先有元素就从哪个弹出

List应用场景

场景1:消息队列

# 生产者:向队列推送任务
127.0.0.1:6379> RPUSH task:queue "send_email:user123"
(integer) 1
127.0.0.1:6379> RPUSH task:queue "process_order:order456"
(integer) 2

# 消费者:从队列获取任务
127.0.0.1:6379> BLPOP task:queue 0
1) "task:queue"
2) "send_email:user123"

# Python实现
import redis
r = redis.Redis()

# 生产者
def produce_task(task):
    r.rpush("task:queue", task)

# 消费者
def consume_task():
    while True:
        task = r.blpop("task:queue", timeout=0)
        if task:
            queue_name, task_data = task
            print(f"Processing: {task_data}")
            # 处理任务...

场景2:最新动态(时间线)

# 发布动态(最新的在前面)
127.0.0.1:6379> LPUSH timeline:user:1001 "发布了新文章"
(integer) 1
127.0.0.1:6379> LPUSH timeline:user:1001 "点赞了一条微博"
(integer) 2
127.0.0.1:6379> LPUSH timeline:user:1001 "评论了一张照片"
(integer) 3

# 获取最新10条动态
127.0.0.1:6379> LRANGE timeline:user:1001 0 9
1) "评论了一张照片"
2) "点赞了一条微博"
3) "发布了新文章"

# 限制动态数量(只保留最新100条)
127.0.0.1:6379> LTRIM timeline:user:1001 0 99
OK

场景3:文章列表(分页)

# 添加文章ID到列表
127.0.0.1:6379> RPUSH articles:latest 1001 1002 1003 1004 1005
(integer) 5

# 分页查询(每页2条)
# 第1页(索引0-1)
127.0.0.1:6379> LRANGE articles:latest 0 1
1) "1001"
2) "1002"

# 第2页(索引2-3)
127.0.0.1:6379> LRANGE articles:latest 2 3
1) "1003"
2) "1004"

# 第3页(索引4-5)
127.0.0.1:6379> LRANGE articles:latest 4 5
1) "1005"

场景4:栈操作(LIFO)

# 使用LPUSH和LPOP实现栈(后进先出)
127.0.0.1:6379> LPUSH stack "A"
(integer) 1
127.0.0.1:6379> LPUSH stack "B"
(integer) 2
127.0.0.1:6379> LPUSH stack "C"
(integer) 3

# 弹出元素(后进先出)
127.0.0.1:6379> LPOP stack
"C"
127.0.0.1:6379> LPOP stack
"B"
127.0.0.1:6379> LPOP stack
"A"

场景5:队列操作(FIFO)

# 使用RPUSH和LPOP实现队列(先进先出)
127.0.0.1:6379> RPUSH queue "task1"
(integer) 1
127.0.0.1:6379> RPUSH queue "task2"
(integer) 2
127.0.0.1:6379> RPUSH queue "task3"
(integer) 3

# 弹出元素(先进先出)
127.0.0.1:6379> LPOP queue
"task1"
127.0.0.1:6379> LPOP queue
"task2"
127.0.0.1:6379> LPOP queue
"task3"

List命令总结

命令 功能 时间复杂度 应用场景
LPUSH/RPUSH 头部/尾部插入 O(1) 消息队列、栈
LPOP/RPOP 头部/尾部弹出 O(1) 消息队列、栈
LRANGE 范围查询 O(N) 分页、时间线
LINDEX 索引查询 O(N) 随机访问
LLEN 获取长度 O(1) 统计
LREM 删除元素 O(N) 清理数据
LTRIM 保留范围 O(N) 限制列表大小
BLPOP/BRPOP 阻塞弹出 O(1) 消息队列
重要提示:

实践练习

练习任务:
  1. 基础操作:创建一个列表,使用LPUSH和RPUSH插入10个元素,然后用LRANGE查看
  2. 消息队列:实现一个简单的消息队列,生产者用RPUSH推送5条消息,消费者用LPOP获取
  3. 时间线:模拟用户动态,用LPUSH添加10条动态,用LRANGE获取最新5条
  4. 分页查询:创建包含20个元素的列表,实现每页5条的分页查询
  5. 栈操作:使用LPUSH和LPOP实现栈,验证后进先出特性
  6. 阻塞队列:使用BLPOP实现阻塞式消息队列,设置10秒超时

总结

本课程深入讲解了Redis List类型的各种操作命令:

List是Redis中非常实用的数据类型,特别适合需要保持顺序的场景。掌握List的各种操作命令和应用场景,能够帮助我们更好地设计和实现各种功能。