2.2 列表 (List)


文档摘要

2.2 列表 (List) Redis 数据类型详解:2. 列表 (List) 2.2 列表 (List):有序、可重复的字符串集合 Redis 列表(List)是一种有序的字符串元素集合。这意味着列表中的元素按照插入顺序排列,可以通过索引访问特定位置的元素。与集合(Set)不同,Redis 列表允许重复元素的存在。可以将 Redis 列表类比为编程语言中的双向链表,它提供了高效地在列表两端添加和删除元素的能力,同时支持通过索引访问元素。 2.2.

2.2 列表 (List)

Redis 数据类型详解:2. 列表 (List)

2.2 列表 (List):有序、可重复的字符串集合

Redis 列表(List)是一种有序的字符串元素集合。这意味着列表中的元素按照插入顺序排列,可以通过索引访问特定位置的元素。与集合(Set)不同,Redis 列表允许重复元素的存在。可以将 Redis 列表类比为编程语言中的双向链表,它提供了高效地在列表两端添加和删除元素的能力,同时支持通过索引访问元素。

2.2.1 列表的特性和优势

  • 有序性 (Ordered):列表中的元素严格按照插入顺序排序,这使得列表非常适合存储需要保持顺序的数据,例如:

    • 用户操作日志

    • 消息队列

    • 最新动态列表

  • 可重复性 (Duplicates Allowed):列表中允许出现重复的元素,这在某些场景下非常有用,例如:

    • 记录用户多次相同的操作

    • 存储商品浏览历史(同一商品可能被多次浏览)

  • 高效的头部和尾部操作 (Efficient Head/Tail Operations):Redis 列表在头部(Left/Head)和尾部(Right/Tail)执行添加和删除操作的时间复杂度为 O(1),这意味着即使列表非常长,这些操作也能保持高效。这得益于 Redis 列表底层数据结构的优化。

  • 灵活性 (Flexibility):Redis 列表提供了丰富的命令,可以实现各种列表操作,例如:

    • 在头部或尾部添加元素 (LPUSH, RPUSH)

    • 从头部或尾部移除元素 (LPOP, RPOP)

    • 获取指定范围的元素 (LRANGE)

    • 根据索引获取元素 (LINDEX)

    • 阻塞式弹出操作 (BLPOP, BRPOP),用于实现消息队列

2.2.2 列表的常用命令及代码实践

接下来,我们通过一系列的代码示例,详细介绍 Redis 列表中常用的命令及其用法。我们将使用 redis-cli 客户端进行演示。

2.2.2.1 添加元素
  • LPUSH key element [element ...]: 将一个或多个元素从列表头部插入列表。如果 key 不存在,则先创建列表再进行插入操作。

    # 从列表 'mylist' 头部插入元素 'item1' redis> LPUSH mylist item1 (integer) 1 # 从列表 'mylist' 头部插入多个元素 'item2' 和 'item3' redis> LPUSH mylist item2 item3 (integer) 3 # 查看列表 'mylist' 的内容 (LRANGE key start stop,获取列表指定范围的元素,0 -1 表示获取所有元素) redis> LRANGE mylist 0 -1 1) "item3" 2) "item2" 3) "item1"

    详解: LPUSH 命令将元素添加到列表的最左侧(头部),因此后添加的元素会位于列表的前面。

  • RPUSH key element [element ...]: 将一个或多个元素从列表尾部插入列表。如果 key 不存在,则先创建列表再进行插入操作。

    # 从列表 'mylist' 尾部插入元素 'item4' redis> RPUSH mylist item4 (integer) 4 # 从列表 'mylist' 尾部插入多个元素 'item5' 和 'item6' redis> RPUSH mylist item5 item6 (integer) 6 # 再次查看列表 'mylist' 的内容 redis> LRANGE mylist 0 -1 1) "item3" 2) "item2" 3) "item1" 4) "item4" 5) "item5" 6) "item6"

    详解: RPUSH 命令将元素添加到列表的最右侧(尾部),因此后添加的元素会位于列表的后面。

  • LINSERT key BEFORE|AFTER pivot element: 将元素 element 插入到列表 key指定元素 pivot 的前面 (BEFORE) 或后面 (AFTER)

    # 在列表 'mylist' 中,元素 'item4' 的前面插入 'item3.5' redis> LINSERT mylist BEFORE item4 item3.5 (integer) 7 # 查看列表 'mylist' redis> LRANGE mylist 0 -1 1) "item3" 2) "item2" 3) "item1" 4) "item3.5" 5) "item4" 6) "item5" 7) "item6" # 在列表 'mylist' 中,元素 'item2' 的后面插入 'item2.5' redis> LINSERT mylist AFTER item2 item2.5 (integer) 8 # 查看列表 'mylist' redis> LRANGE mylist 0 -1 1) "item3" 2) "item2" 3) "item2.5" 4) "item1" 5) "item3.5" 6) "item4" 7) "item5" 8) "item6"

    详解: LINSERT 命令允许在列表的中间位置插入元素,提供了更精细的控制。如果 pivot 元素不存在,则插入操作失败,列表保持不变。

  • LPUSHX key element / RPUSHX key element: 仅当列表 key 存在时,才从列表头部 (LPUSHX) 或尾部 (RPUSHX) 插入元素。如果 key 不存在,则不进行任何操作。

    # 列表 'mylist' 已经存在,使用 LPUSHX 添加元素 'item0' redis> LPUSHX mylist item0 (integer) 9 # 查看列表 'mylist' redis> LRANGE mylist 0 -1 1) "item0" 2) "item3" 3) "item2" 4) "item2.5" 5) "item1" 6) "item3.5" 7) "item4" 8) "item5" 9) "item6" # 列表 'nonexistent_list' 不存在,使用 LPUSHX 添加元素 'item',操作无效 redis> LPUSHX nonexistent_list item (integer) 0 # 列表 'nonexistent_list' 仍然不存在 redis> EXISTS nonexistent_list (integer) 0

    详解: LPUSHXRPUSHX 命令用于在已知列表存在的情况下添加元素,可以避免误操作创建新的列表。

2.2.2.2 移除元素
  • LPOP key: 移除并返回列表 key头部元素。当 key 不存在或列表为空时,返回 nil

    # 从列表 'mylist' 头部移除元素 redis> LPOP mylist "item0" # 查看列表 'mylist',头部元素 'item0' 已被移除 redis> LRANGE mylist 0 -1 1) "item3" 2) "item2" 3) "item2.5" 4) "item1" 5) "item3.5" 6) "item4" 7) "item5" 8) "item6" # 当列表为空时,LPOP 返回 nil redis> DEL emptylist # 先删除 emptylist (如果存在) (integer) 0 redis> LPOP emptylist (nil)

    详解: LPOP 命令从列表头部移除元素,并返回被移除的元素。这是一种破坏性的操作,会修改列表本身。

  • RPOP key: 移除并返回列表 key尾部元素。当 key 不存在或列表为空时,返回 nil

    # 从列表 'mylist' 尾部移除元素 redis> RPOP mylist "item6" # 查看列表 'mylist',尾部元素 'item6' 已被移除 redis> LRANGE mylist 0 -1 1) "item3" 2) "item2" 3) "item2.5" 4) "item1" 5) "item3.5" 6) "item4" 7) "item5"

    详解: RPOP 命令与 LPOP 类似,但它从列表尾部移除元素。

  • LREM key count element: 根据参数 count 的值,移除列表中与 element 相等的元素。

    • count > 0: 从列表头部开始,移除最多 count 个与 element 相等的元素。

    • count < 0: 从列表尾部开始,移除最多 |count| 个与 element 相等的元素。

    • count = 0: 移除列表中所有element 相等的元素。

    # 创建一个包含重复元素的列表 'mylist2' redis> RPUSH mylist2 a b c a b a (integer) 6 # 从头部开始,移除 2 个 'a' 元素 redis> LREM mylist2 2 a (integer) 2 redis> LRANGE mylist2 0 -1 1) "b" 2) "c" 3) "b" 4) "a" # 从尾部开始,移除 1 个 'b' 元素 redis> LREM mylist2 -1 b (integer) 1 redis> LRANGE mylist2 0 -1 1) "b" 2) "c" 3) "a" # 移除所有 'b' 元素 redis> LREM mylist2 0 b (integer) 1 redis> LRANGE mylist2 0 -1 1) "c" 2) "a"

    详解: LREM 命令提供了灵活的元素移除方式,可以根据元素值和移除数量进行精确控制。

  • LTRIM key start stop: 修剪列表 key只保留列表中索引在 startstop 之间的元素,闭区间。startstop 可以为负数,负数索引表示从尾部开始计数,-1 表示最后一个元素,-2 表示倒数第二个元素,以此类推。

    # 列表 'mylist' 当前内容: "item3" "item2" "item2.5" "item1" "item3.5" "item4" "item5" redis> LRANGE mylist 0 -1 1) "item3" 2) "item2" 3) "item2.5" 4) "item1" 5) "item3.5" 6) "item4" 7) "item5" # 保留索引 1 到 3 之间的元素 (item2, item2.5, item1) redis> LTRIM mylist 1 3 "OK" redis> LRANGE mylist 0 -1 1) "item2" 2) "item2.5" 3) "item1" # 保留从头部开始到倒数第二个元素 redis> LTRIM mylist 0 -2 "OK" redis> LRANGE mylist 0 -1 1) "item2" 2) "item2.5"

    详解: LTRIM 命令用于截取列表的片段,可以有效地控制列表的长度,例如,只保留最新的 N 条记录。

2.2.2.3 获取元素
  • LINDEX key index: 返回列表 key 中索引为 index 的元素。索引从 0 开始,正数索引表示从头部开始计数,负数索引表示从尾部开始计数。如果 index 超出列表范围,返回 nil

    # 列表 'mylist' 当前内容: "item2" "item2.5" redis> LRANGE mylist 0 -1 1) "item2" 2) "item2.5" # 获取索引为 0 的元素 (头部元素) redis> LINDEX mylist 0 "item2" # 获取索引为 1 的元素 redis> LINDEX mylist 1 "item2.5" # 获取索引为 -1 的元素 (尾部元素) redis> LINDEX mylist -1 "item2.5" # 索引超出范围,返回 nil redis> LINDEX mylist 2 (nil)

    详解: LINDEX 命令允许通过索引随机访问列表中的元素,时间复杂度为 O(N),其中 N 是索引到列表头部或尾部的距离。

  • LRANGE key start stop: 返回列表 key 中索引在 startstop 之间的元素,闭区间。 startstop 可以为负数索引。

    # 列表 'mylist' 当前内容: "item2" "item2.5" redis> LRANGE mylist 0 -1 1) "item2" 2) "item2.5" # 获取索引 0 到 0 之间的元素,即只获取头部元素 redis> LRANGE mylist 0 0 1) "item2" # 获取索引 0 到 1 之间的元素,即获取所有元素 redis> LRANGE mylist 0 1 1) "item2" 2) "item2.5" # 获取索引 0 到 -1 之间的元素,也表示获取所有元素 redis> LRANGE mylist 0 -1 1) "item2" 2) "item2.5" # 获取索引 -2 到 -1 之间的元素,即获取最后两个元素 redis> LRANGE mylist -2 -1 1) "item2" 2) "item2.5"

    详解: LRANGE 命令可以批量获取列表中的元素,常用于分页查询或获取列表的子集。

  • LLEN key: 返回列表 key 的长度(列表中元素的数量)。如果 key 不存在,则被视为空列表,返回 0。

    # 列表 'mylist' 当前内容: "item2" "item2.5" redis> LRANGE mylist 0 -1 1) "item2" 2) "item2.5" # 获取列表长度 redis> LLEN mylist (integer) 2 # 获取不存在的列表的长度 redis> LLEN nonexist_list (integer) 0

    详解: LLEN 命令用于快速获取列表的长度,时间复杂度为 O(1)。

2.2.2.4 阻塞式弹出操作 (Blocking Pop)

BLPOPBRPOP 是列表的阻塞式弹出命令,常用于实现消息队列

  • BLPOP key [key ...] timeout: 阻塞式地移除并返回第一个非空列表头部元素。如果所有给定 key 对应的列表都为空,则命令会阻塞客户端,直到等待超时 (timeout 秒) 或有非空列表出现。timeout 为 0 表示永久阻塞。

  • BRPOP key [key ...] timeout: 阻塞式地移除并返回第一个非空列表尾部元素。行为与 BLPOP 类似,只是操作的是列表尾部。

    # 客户端 1 执行 BLPOP 命令,阻塞等待 'task_queue' 列表 redis> BLPOP task_queue 10 # 超时时间 10 秒 (nil) # 10 秒后超时返回 nil # 客户端 2 向 'task_queue' 列表添加一个任务 redis> LPUSH task_queue task1 (integer) 1 # 客户端 1 立即解除阻塞,并返回弹出的元素 redis> BLPOP task_queue 10 1) "task_queue" 2) "task1"

    详解: BLPOPBRPOP 命令的关键在于阻塞特性。当消费者客户端执行这些命令时,如果队列为空,客户端不会立即返回,而是进入阻塞状态,等待生产者客户端向队列中添加元素。一旦有元素添加到队列,阻塞的客户端会被唤醒,并弹出元素进行处理。这有效地避免了消费者客户端轮询检查队列状态的资源浪费。

  • BRPOPLPUSH source destination timeout: 阻塞式地从列表 source尾部弹出一个元素,并将其原子性地推入列表 destination头部,并返回这个元素。如果 source 列表为空,则命令会阻塞客户端,直到等待超时 (timeout 秒) 或 source 列表变为非空。

    # 客户端 1 执行 BRPOPLPUSH 命令,阻塞等待 'queue1' 列表 redis> BRPOPLPUSH queue1 processing_queue 10 # 客户端 2 向 'queue1' 列表添加一个任务 redis> RPUSH queue1 taskA (integer) 1 # 客户端 1 立即解除阻塞,并返回弹出的元素,同时 'taskA' 被移动到 'processing_queue' 头部 redis> BRPOPLPUSH queue1 processing_queue 10 "taskA" # 查看 'processing_queue' 列表,'taskA' 已在其中 redis> LRANGE processing_queue 0 -1 1) "taskA"

    详解: BRPOPLPUSH 命令提供了一种可靠的消息队列模式。元素从源队列弹出后,会立即被推入目标队列,即使消费者客户端在处理过程中崩溃,任务也不会丢失,可以从目标队列中重新获取。这常用于实现可靠的任务队列循环队列

2.2.2.5 修改元素
  • LSET key index element: 将列表 key 中索引为 index 的元素的值设置为 element。当 index 超出列表范围时,或者 key 不存在时,会返回错误。

    # 列表 'mylist' 当前内容: "item2" "item2.5" redis> LRANGE mylist 0 -1 1) "item2" 2) "item2.5" # 将索引为 0 的元素修改为 'item2_updated' redis> LSET mylist 0 item2_updated "OK" # 查看列表 'mylist' redis> LRANGE mylist 0 -1 1) "item2_updated" 2) "item2.5" # 索引超出范围,返回错误 redis> LSET mylist 2 new_item (error) ERR index out of range

    详解: LSET 命令允许修改列表中指定位置的元素。时间复杂度为 O(N),与 LINDEX 类似。

  • RPOPLPUSH source destination: 非阻塞式地从列表 source尾部弹出一个元素,并将其原子性地推入列表 destination头部,并返回这个元素。与 BRPOPLPUSH 类似,但不具备阻塞特性。

    # 从 'mylist' 尾部弹出一个元素,并推入 'another_list' 头部 redis> RPOPLPUSH mylist another_list "item2.5" # 查看 'mylist',尾部元素已被移除 redis> LRANGE mylist 0 -1 1) "item2_updated" # 查看 'another_list',头部新增了 'item2.5' redis> LRANGE another_list 0 -1 1) "item2.5"

    详解: RPOPLPUSH 命令用于在两个列表之间移动元素,常用于构建安全队列循环队列,在不需要阻塞特性的场景下使用。

2.2.3 列表的应用场景

  • 消息队列 (Message Queue):Redis 列表的 LPUSH/RPUSHBRPOP/BLPOP 命令组合,可以高效地实现消息队列。生产者使用 LPUSH/RPUSH 向列表添加消息,消费者使用 BRPOP/BLPOP 阻塞式地从列表获取消息。

  • 任务队列 (Task Queue):基于消息队列,可以构建更复杂的任务队列系统。结合 BRPOPLPUSH 命令,可以实现可靠的任务处理流程,确保任务不丢失。

  • 最新动态列表 (Latest News Feed):使用 LPUSH 命令向列表头部添加最新动态,使用 LTRIM 命令限制列表的长度,只保留最新的 N 条动态。可以使用 LRANGE 命令分页获取动态列表。

  • 文章列表/评论列表 (Article/Comment List):类似于最新动态列表,可以使用列表存储文章或评论列表,按照发布时间排序。

  • 用户操作历史记录 (User Activity Log):记录用户的操作历史,例如浏览记录、搜索记录等。可以使用 LPUSH 添加记录,使用 LTRIM 限制记录数量,使用 LRANGE 获取历史记录。

  • 循环队列 (Circular Queue):使用 RPOPLPUSH 命令可以将列表尾部的元素移动到头部,实现循环队列的效果。

2.2.4 列表的底层实现

Redis 列表的底层实现根据列表的长度和元素类型,可能会采用不同的数据结构进行优化:

  • ziplist (压缩列表):当列表元素个数较少且元素长度较小时,Redis 会使用 ziplist 作为列表的底层实现。ziplist 是一种紧凑的数据结构,可以节省内存空间。

  • linkedlist (链表):当列表元素个数较多或元素长度较大时,Redis 会将列表的底层实现转换为 linkedlistlinkedlist 是双向链表,更适合存储大量元素,支持高效的头部和尾部操作。

  • quicklist (快速列表):Redis 3.2 版本引入了 quicklist,它结合了 ziplistlinkedlist 的优点。quicklist 是一个链表,但链表的每个节点是一个 ziplist。这样既可以节省内存空间,又能兼顾链表的性能。当列表长度或元素大小超出一定阈值时,Redis 会自动将 ziplist 转换为 quicklist

2.2.5 列表的注意事项和最佳实践

  • 列表长度限制: 虽然 Redis 列表理论上可以存储非常多的元素,但过长的列表会影响性能,特别是 LINDEX 等需要遍历列表的操作。建议根据实际应用场景,合理控制列表的长度。

  • 命令选择: 根据不同的应用场景,选择合适的列表命令。例如,如果需要实现消息队列,应使用 BRPOP/BLPOP 等阻塞式命令。如果需要获取列表片段,应使用 LRANGELTRIM 命令。

  • 内存占用: 存储大量元素的列表会占用较多内存。需要根据实际数据量和内存资源进行评估和规划。可以使用 LTRIM 命令定期清理过期的或不再需要的列表元素,以节省内存空间。

  • 避免过度使用 LINSERTLSET: LINSERTLSET 命令的时间复杂度较高 (O(N)),在需要频繁在列表中间位置插入或修改元素的场景下,需要谨慎使用,并考虑是否可以使用其他更合适的数据结构。

2.2.6 总结

Redis 列表作为一种有序、可重复的数据结构,在构建各种应用场景中都非常实用。它提供了丰富的命令,支持高效的头部和尾部操作,以及阻塞式弹出等高级特性。理解 Redis 列表的特性、命令和底层实现,并结合最佳实践,可以帮助我们更好地利用 Redis 列表构建高性能、可靠的应用系统。无论是简单的最新动态列表,还是复杂的消息队列和任务队列,Redis 列表都能胜任。掌握列表的使用,是深入理解和应用 Redis 的重要一步。


发布者: 作者: 转发
评论区 (0)
U