Redis 数据结构之 List 详细解析
Redis 数据结构详解List 列表篇在 Redis 的五大基础数据结构中List列表是一种非常灵活的有序字符串集合它既可以充当栈和队列也能实现消息队列、时间线等业务场景。本文将带你从底层特性、核心命令、内部编码到实战场景全面吃透 Redis List。一、List 列表是什么List 是 Redis 中存储多个有序字符串的序列每个元素称为element一个列表最多可以存储2 32 − 1 2^{32}-1232−1个元素。它的核心特性可以概括为三点有序性列表中的元素是按插入顺序排列的我们可以通过下标正索引 / 负索引获取指定元素例如获取第 5 个元素用index 4或直接用负索引lindex \-1获取最后一个元素。元素可重复列表中允许存在相同的值比如[“a”, “b”, “a”] 这样的结构是完全合法的。操作灵活性支持从两端左 / 右插入、弹出元素也支持按范围截取、按索引修改能同时适配栈、队列等多种数据结构的使用需求。List 的基础操作示意图如下二、List 核心命令详解List 的命令可以分为基础操作和阻塞操作两大类我们按功能逐一拆解说明。1. 基础操作命令1添加元素LPUSH / LPUSHX / RPUSH / RPUSHX这组命令用于向列表中插入元素区别在于插入方向和是否仅在列表存在时操作。命令作用语法时间复杂度LPUSH从列表左侧表头插入一个或多个元素LPUSH key element [element \.\.\.\]单个元素O ( 1 ) O(1)O(1)多个元素O ( N ) O(N)O(N)LPUSHX仅当列表存在时从左侧插入元素LPUSHX key element [element \.\.\.\]单个元素O ( 1 ) O(1)O(1)多个元素O ( N ) O(N)O(N)RPUSH从列表右侧表尾插入一个或多个元素RPUSH key element [element \.\.\.\]单个元素O ( 1 ) O(1)O(1)多个元素O ( N ) O(N)O(N)RPUSHX仅当列表存在时从右侧插入元素RPUSHX key element [element \.\.\.\]单个元素O ( 1 ) O(1)O(1)多个元素O ( N ) O(N)O(N)示例# 从左侧插入元素 127.0.0.1:6379 LPUSH mylist world (integer) 1 127.0.0.1:6379 LPUSH mylist hello (integer) 2 # 查看列表所有元素 127.0.0.1:6379 LRANGE mylist 0 -1 1) hello 2) world2获取元素LRANGE / LINDEX / LLEN这组命令用于读取列表中的元素信息不修改列表本身。LRANGE key start stop获取列表中从start到stop索引区间的所有元素左闭右闭支持负索引\-1表示最后一个元素。LINDEX key index获取列表中指定索引位置的元素索引超出范围返回nil。LLEN key获取列表的长度时间复杂度为O ( 1 ) O(1)O(1)。示例# 继续上面的示例 127.0.0.1:6379 LRANGE mylist 0 -1 1) hello 2) world 127.0.0.1:6379 LINDEX mylist 0 hello 127.0.0.1:6379 LINDEX mylist -1 world 127.0.0.1:6379 LLEN mylist (integer) 23插入 / 修改 / 删除元素LINSERT / LREM / LTRIM / LSET这组命令用于修改列表的结构或元素值。LINSERT key BEFORE|AFTER pivot element在列表中指定元素pivot的前 / 后插入新元素。LREM key count value删除列表中前count个值为value的元素count\gt;0从左删count\lt;0从右删count0删除所有匹配元素。LTRIM key start stop截取列表中start到stop区间的元素其余元素全部删除常用于实现固定长度列表。LSET key index value修改列表中指定索引位置的元素值索引超出范围会报错。示例# 在 world 前插入 there 127.0.0.1:6379 LINSERT mylist BEFORE world there (integer) 3 127.0.0.1:6379 LRANGE mylist 0 -1 1) hello 2) there 3) world # 修改索引1的元素为 hi 127.0.0.1:6379 LSET mylist 1 hi OK 127.0.0.1:6379 LRANGE mylist 0 -1 1) hello 2) hi 3) world2. 阻塞操作命令BLPOP / BRPOPBLPOP和BRPOP是LPOP和RPOP的阻塞版本它们的核心区别在于非阻塞版LPOP/RPOP如果列表为空直接返回nil阻塞版BLPOP/BRPOP如果列表为空会阻塞等待指定时间直到有新元素加入或超时才返回结果。命令语法BLPOP key [key ...] timeout BRPOP key [key ...] timeoutkey要监听的列表键可以同时监听多个timeout阻塞等待的超时时间单位秒0表示永久阻塞。核心特性多键监听可以同时监听多个列表按顺序检查列表哪个列表有元素就从哪个列表弹出。客户端公平性多个客户端同时阻塞等待同一个列表时遵循 “先到先得” 原则先执行命令的客户端会优先获取元素。超时机制超时时间到了仍无元素加入返回nil。阻塞 vs 非阻塞对比场景非阻塞版LPOP阻塞版BLPOP列表不为空直接弹出元素返回结果直接弹出元素返回结果列表为空立即返回nil阻塞等待直到有元素加入或超时示意图如下三、List 内部编码实现Redis 为了兼顾性能和内存占用为 List 设计了两种内部编码会根据列表的元素数量和元素长度自动切换1. ziplist压缩列表当列表同时满足以下两个条件时使用ziplist作为内部编码列表中元素数量小于list\-max\-ziplist\-entries默认 512每个元素的长度都小于list\-max\-ziplist\-value默认 64 字节。ziplist是一种紧凑的连续内存结构通过减少额外指针和元数据来节省内存适合存储少量、短字符串元素。2. linkedlist双向链表当列表不满足ziplist的条件时Redis 会自动切换为linkedlist编码元素数量超过 512 个任意一个元素长度超过 64 字节。linkedlist是标准的双向链表结构每个节点包含前驱 / 后继指针和元素值虽然内存占用比ziplist高但插入、删除元素的时间复杂度为O ( 1 ) O(1)O(1)适合处理大量元素或长字符串元素。示例# 查看小列表的编码 127.0.0.1:6379 LPUSH smalllist a b c (integer) 3 127.0.0.1:6379 OBJECT ENCODING smalllist ziplist # 查看长字符串列表的编码 127.0.0.1:6379 LPUSH biglist a very long string that exceeds 64 bytes... (integer) 1 127.0.0.1:6379 OBJECT ENCODING biglist linkedlist四、List 典型使用场景List 的特性决定了它非常适合处理 “有序、需频繁两端操作” 的业务场景以下是最常见的两个场景1. 阻塞式消息队列生产者 - 消费者模型利用LPUSH生产者写入和BRPOP消费者读取可以实现经典的阻塞式消息队列生产者使用LPUSH将消息写入列表消费者使用BRPOP阻塞等待消息有消息时立即处理无消息时阻塞等待避免轮询空列表消耗资源。示意图如下如果需要实现多消费者的负载均衡可以同时监听多个列表键实现 “发布 - 订阅” 模式的扩展。2. 社交平台时间线Timeline微博、朋友圈这类社交平台的时间线功能本质上就是基于 List 实现的存储单条动态每条微博 / 朋友圈作为一条数据存入 Hash 结构中构建时间线列表用户发布动态时用LPUSH将动态 ID 写入用户的时间线列表user:1:blogs保证最新的动态在列表最前面分页查询使用LRANGE user:1:blogs 0 9分页获取动态 ID再批量查询 Hash 中的动态详情。这种实现方式既保证了时间线的有序性又支持高效的分页查询是 List 最经典的实战场景之一。五、List 命令时间复杂度总结为了方便大家快速回顾这里整理了 List 所有核心命令的时间复杂度操作类型命令时间复杂度添加LPUSH/RPUSH/LPUSHX/RPUSHX单个元素O ( 1 ) O(1)O(1)多个元素O ( N ) O(N)O(N)查找LRANGEO ( N ) O(N)O(N)N 为返回元素个数LINDEX/LLENO ( 1 ) O(1)O(1)修改LINSERT/LSETO ( N ) O(N)O(N)N 为列表长度删除LREM/LTRIMO ( N ) O(N)O(N)N 为列表长度阻塞操作BLPOP/BRPOPO ( 1 ) O(1)O(1)List 作为 Redis 中唯一的有序序列结构凭借灵活的操作和高效的性能在消息队列、时间线等场景中有着不可替代的作用。理解它的底层编码和命令特性才能在业务中选择最合适的实现方式。