目录
- list简单介绍
- lpush
- lpushx
- rpush
- rpushx
- lrange
- lpop
- rpop
- lindex
- linsert
- llen
- [blpop 和 brpop](#blpop 和 brpop)
- lrem
- ltrim
- 编码方式
- 使用场景
list简单介绍
列表类型是用来存储多个有序的字符串,如图所示,a、b、c、d、e 五个元素从左到右组成了一个有序的列表,列表中的每个字符串称为元素,一个列表最多可以存储 2^32 - 1 个元素。在 Redis 中,可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用场景。


列表类型的特点:
(1)列表中的元素是有序的,这意味着可以通过索引下标获取某个元素或者某个范围的元素列表,就像数组一样,这里的有序不是指顺序降序,而是顺序表一样按照插入顺序排列着,可以随机访问。
(2)区分获取和删除的区别,例如上图中的 lrem 1 b 是从列表中把从左数遇到的前 1 个 b 元素删除,这个操作会导致列表的长度从 5 变成 4;但是执行 lindex 4 只会获取元素,但列表长度是不会变化的。
(3)列表中的元素是允许重复的,因为其定位类似数组,不是哈希。
lpush
将一个或者多个元素从左侧放入(头插)到 list 中。


时间复杂度是O(1),且这个操作是头插,lpush 的 l 是 left 的意思,所以先插入的在最后面。lpush 的 key 如果不存在,那么会自动创建插入,如果存在,value 不是 list 也会报错。
lpushx
在 key 存在时,将一个或者多个元素从左侧放入(头插)到 list 中。不存在,直接返回。


可以看到,如果 lpushx 的 key 不存在,那么就会直接返回0,lpushx 会返回插入成功后的 list 元素数量。
rpush
将一个或者多个元素从右侧放入(尾插)到 list 中。


rpushx
在 key 存在时,将一个或者多个元素从右侧放入(尾插)到 list 中。

lrange
获取从 start 到 end 区间的所有元素,左闭右闭。


注意这里是左闭右闭,而且支持倒数下标,-1 表示倒数第一个,以此类推。

lpop
从 list 左侧取出元素(即头删)。


带参数的功能是 Redis 6.2 版本新加的,我们也可以不带,不带就是删一个,该指令会返回删除的元素的值。
rpop
从 list 右侧取出元素(即尾删)。

lindex
获取从左数第 index 位置的元素。


如果是非法下标,则返回 nil。注意这里的时间复杂度是O(N),这个比较反直觉,因为其并不是真的一块连续的空间,能像数组一样随机访问,还是需要遍历的。
linsert
在特定位置插入元素。


这个指令很类似我们静态数组的 [] 查找元素。注意这里是给定一个基准值,从左向右遍历,找到第一个基准值,根据 before(之前) | after(之后)进行插入。如果从头到尾都没有找到,就会返回 -1。该指令返回插入之后的 list 长度。
llen
获取 list 长度。


这里的 list 的长度是指 list 的元素数量。
blpop 和 brpop
blpop 和 brpop 是 lpop 和 rpop 的阻塞版本。他们有什么区别呢?
在列表中有元素的情况下,阻塞和非阻塞表现是一致的。但如果列表中没有元素,非阻塞版本会理解返回 nil,但阻塞版本会根据 timeout,阻塞一段时间,期间 Redis 可以执行其他命令,但要求执行该命令的客户端会表现为阻塞状态。
命令中如果设置了多个键,那么会从左向右进行遍历键,一旦有一个键对应的列表中可以弹出元素,命令立即返回。
如果多个客户端同时对一个键执行 pop,则最先执行命令的客户端会得到弹出的元素,也就是会根据先来后到排个队,因为之前也说过,Redis 可以用来作为消息队列,也就是生产消费者模型,早期 list 就可以用来做消息队列,这个机制可以用来防止消费者饥饿问题。



可以看到,第一次 blpop 时因为 list 为空所以阻塞了,插入元素后就弹出对应值返回了,还标明了操作时间,因为可以同时 blpop 多个 key,所以这里返回时还会表明哪个 key 的。如果一直没有元素,就会超时返回 nil。
lrem
从 list 中删除指定值的元素


该指令就是指定元素值和数量,然后删除对应数量的元素。但是该指令的 count 有以下规则:
count > 0:从表头到表尾遍历,删除前 count 个等于 value 的元素;
count < 0:从表尾到表头遍历,删除前 abs(count) 个等于 value 的元素;
count = 0:删除 List 中所有等于 value 的元素;
此外,如果 count 数量超过了 list 中实际存在的该元素数,不会报错,而是适应一下,该元素全部删除。
ltrim
修剪 List(裁剪列表) 的核心命令,简单说就是保留 List 中指定索引范围的元素,删除范围外的所有元素,常用来实现「固定长度的列表」(比如最近 N 条日志、消息队列长度限制)。


可以看到范围是左闭右闭的,且支持倒数,且可以适应参数。
编码方式
以前的列表类型的内部编码有两种:
ziplist(压缩列表):当列表的元素个数小于 list-max-ziplist-entries 配置(默认 512 个),同时列表中每个元素的长度都小于 list-max-ziplist-value 配置(默认 64 字节)时,Redis 会选用ziplist 来作为列表的内部编码实现来减少内存消耗。
linkedlist(链表):当列表类型无法满足 ziplist 的条件时,Redis 会使用 linkedlist 作为列表的内部实现。
但是现在被舍弃了,现在不管个数和长度,都是用的 quicklist,quicklist可以认为是 ziplist 和 linkedlist 的合体,整体是一个链表,但是节点是一个个压缩链表。

使用场景
Redis 列表类型的核心用途主要体现在两大场景:一是作为消息队列使用,利用 lpush+brpop 的命令组合实现阻塞式生产者 - 消费者模型,生产者通过 lpush 向列表插入消息,多个消费者借助 brpop 阻塞式争抢队列元素,既保证消费的负载均衡与高可用性,还能通过不同键名模拟分频道队列,让消费者针对性监听不同键实现消息分频道订阅;二是用于实现有序的时序类列表展示,典型如微博 Timeline 场景,将用户的微博标识存入专属列表,借助 lpush 维护最新在前的有序性,通过 lrange 按索引范围分页获取内容,满足分页展示需求,虽存在 1+n 查询、中间元素获取效率低等问题,但可通过 pipeline 批量操作、列表拆分等方式优化。