# 书接上回
上一篇文章 Redis 的数据结构 string 我们一起学习了这种类型的常用命令,并且还学习了 Redis 中的字符串的结构表示以及好处,这里我们接着学习另外一种数据结构 list 。
# list 简介
list , 一般都会称为列表。在 Redis 中,这种数据结构是一种比较灵活的结构,由于其元素的是有序的,所以可以充当栈和队列这两种数据结构。实际在开发总也有很多应用场景。
一个 List 最多可以包含 2^32-1 个元素。
很多人都会以为 list 是用数组来实现的,非也,非也。它内部是 quicklist 这种数据结构。想要先睹为快的,那么坐电梯直达吧。
# list 的相关命令
# LPUSH 命令
- 语法
LPUSH key value [value …]
- 解释
lpush : left push 。
将一个或者多个值插入到列表 key 的表头,返回列表的长度。元素可以是重复的。
如果 key 不存在,那么会先穿件一个列表,然后再执行 push 操作.
如果 key 值存在,但是 value 类型不是列表类型时,会返回一个错误。
- 演示
1 | 设置一个列表 |
# lpushx 命令
- 语法
LPUSHX key value
- 解释
仅当 key 存在的时候,才将 value 插入列表的表头。返回列表中元素的个数。
- 演示
1 | # 当key值不存在的时候,不会放入列表中 |
# rpush 命令
- 语法
RPUSH key value [value ...]
- 解释
rpush 就是 right push 。将一个或多个值 value 插入到列表 key 的表尾 (最右边)。返回列表的长度。
如果 key 不存在的时候,会创建一个空列表,然后在执行 rpush 操作。
如果 key 存在,但是不是一个列表类型时,返回一个错误。
- 演示
1 | 往列表中加入数据 |
# rpushx 命令
- 语法
rpushx key value
- 解释
与 lpushx 类似,如果 key 不存在时,什么都不会操作。如果 key 存在,才会将元素添加到表尾。
- 演示
1 | key不存在的时候,不会插入数据 |
# lpop 命令
- 语法
LPOP key
- 解释
left pop ;
移除并返回列表的头元素。当 key 不存在的时候,返回 nil
- 演示
1 | key不存在的时候,返回nil |
tip:
lpush+lpop=> 栈,rpush+lpop=> 队列。
# rpop 命令
- 语法
rpop key
- 解释
rpop : right pop ;
和 lpop 相反。移除并返回列表的尾元素。如果 key 不存在返回 nil。
- 演示
1 | # key 不存在,返回nil |
tip:
lpush+rpop=> 队列,rpush+rpop=> 栈。
# lrange 命令
- 语法
LRANGE key start stop
- 解释
获取指定区间内的元素。0 表示第一个元素。如果超过了实际范围就返回空数组。
- 演示
1 | 127.0.0.1:6379> LRANGE k22 0 10 |
# rpoplpush 命令
- 语法
RPOPLPUSH source destination
- 解释
将 source 的尾元素插入到 destination 列表的头元素中,返回该元素。 注意,这是一个原子操作。
比如: source : a,b,c
distination : 1,2,3
使用 RPOPLPUSH source distination , 则:
source : a,b
distination : c,1,2,3
- 演示
1 | # 设置列表1 |
# lrem 命令
- 语法
LREM key count value
- 解释
至多移除列表中 count 个与参数 value 相等的元素。
有以下情況:
count > 0 : 从表头开始向表尾搜索,移除与 value 相等的元素,最多移除 count 个 。
count < 0 : 从表尾开始向表头搜索,移除与 value 相等的元素,最多移除 |count| 个。
count = 0 : 移除表中所有与 value 相等的值。
- 演示
1 | 演示 count>0 时 |
# llen 命令
- 语法
LLEN key
- 解释
获取列表的长度。
如果 key 不存在的时候,返回 0 .
如果 key 对应类型不是 list ,则返回一个错误。
- 演示
1 | 127.0.0.1:6379> llen k30 |
# lindex 命令
- 语法
lindex key index
- 解释
返回列表中,下标为 index 的元素. -1 表示列表的最后一个元素,如果 key 不存在,或者 index 超出范围,返回 nil , 如果 key 不是一个列表类型,返回一个错误。
- 演示
1 | 127.0.0.1:6379> lpush k31 v31_3 v31_2 v31_1 |
# linsert 命令
- 语法
linsert key BEFORE|AFTER pivot value
- 解释
将 value 插入到 key 队列 pivot 值之前或者之后。返回插入完成之后列表的长度。
如果 pivot 不存在 或者 key 不存在,不执行任何操作。
如果 key 对应的不是一个列表类型,返回一个错误。
- 演示
1 | 127.0.0.1:6379> linsert k32 BEFORE k31_1 k31_0 |
# lset 命令
- 语法
lset key index value
- 解释
将列表中的 索引为 index 的值设置为 value 。 如果 index 超出范围,则返回一个错误
- 演示
1 | 127.0.0.1:6379> lpush k33 v33_3 v33_1 |
# ltrim 命令
- 语法
ltrim key start stop
- 解释
保留列表从 start 到 stop 之间的元素。其他元素都将被删除。 注意:包含 (不删除) start 和 stop 两个元素.
如果 key 不存在,直接返回 OK , 如果 key 对应的不是列表,直接返回错误。
- 演示
1 | 127.0.0.1:6379> lpush k34 v34_1 v34_2 v34_3 v34_4 v34_5 v34_6 |
# blpop 命令
- 语法
BLPOP key [key ...] timeout
- 解释
lpop 的 阻塞版本。 block left pop
当给定列表内没有任何元素可供弹出的时候,连接将被 BLPOP 命令阻塞,直到等待超时或发现可弹出元素为止。
当给定多个 key 参数时,按参数 key 的先后顺序依次检查各个列表,弹出第一个非空列表的头元素。
- 演示
1 | # push到三组列表,分别三个元素 |
# brpop 命令
- 语法
BRPOP key [key ...] timeout
- 解释
rpop 的阻塞版本。 block right pop
当给定多个 key 的时候,按照 key 的先后顺序依次检查各个列表。直到弹出一个元素或者超时。
- 演示
1 | # 设置两个列表 |
Tips:
lpush+brpop=> 阻塞队列。
# brpoplpush 命令
- 语法
BRPOPLPUSH source destination timeout
- 解释
rpoplpush 的阻塞版本。 block right left push 。
当列表 source 为空的时候,该命令将阻塞,直到超时,或者 source 中有一个元素可以 pop。
- 演示
1 | # 设置一个列表 |
# list 内部结构之 quicklist
# quicklist
我们来看一下 list 的内部实现 quicklist 结构.
特别注明: quicklist 是双向的链表结构。
在 Redis 中使用如下结构体表示.
1 | typedef struct quicklist { |
quicklist 是回一个通用的双向链接快速列表实现。它的每个节点用 quicklistNode 表示。
一起来看下 qucklistNode 是什么吧。
# quicklistNode
1 | typedef struct quicklistNode { |
quicklistNode 是一个 32byte 的结构体,用于描述一个 quicklist 的一个节点。从代码中可看出,使用了位图来节约空间。在上面的代码中我们还提到两种数据结构,对应的是代码中 zl 指针,指向的位置,如果数据被压缩,指向 quicklistLZF 和 没有数据没有被压缩就是指向 ziplist .
# ziplist
ziplist 这种结构比较复杂,而且在源码中也没有给出明确定义。那 ziplist 这么神秘的结构到底是什么样的呢?
别着急,我们先大体熟悉下 ziplist 这种结构的设计意图。
ziplist 是一个经过特殊编码的双向链表,它的设计意图就是 提高存储效率, ziplist 可以用于存储字符串或者整数,其中整数是按照真正的二进制进行编码的。 它能以 O(1) 的效率在表的两端进行 pop 和 push 操作。
我们都知道,普通的链表每项都是一块独立的内存空间,各项之间都是通过指针连接起来的。这种方式,会带来大量的空间碎片,指针引用也会占用部分空间内存。所以 ziplist 是将表中每项放在连续的空间内存中 (类似数组), ziplist 还对值采取了一个可变长度的存储方式,大的值就用大空间,小的值就用小空间。
# ziplist 结构的官方定义。
The general layout of the ziplist is as follows:
<zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>
<uint32_t zlbytes>is an unsigned integer to hold the number of bytes that the ziplist occupies, including the four bytes of the zlbytes field itself. This value needs to be stored to be able to resize the entire structure without the need to traverse it first.
<uint32_t zltail>is the offset to the last entry in the list. This allows a pop operation on the far side of the list without the need for full traversal.
<uint16_t zllen>is the number of entries. When there are more than 2^16-2 entries, this value is set to 2^16-1 and we need to traverse the entire list to know how many items it holds.
<uint8_t zlend>is a special entry representing the end of the ziplist. Is encoded as a single byte equal to 255. No other normal entry starts with a byte set to the value of 255.
根据上面中解释我们可以得出以下这种模型:

如果没有特殊指定的话,都是采用小尾数法存储的。
-
zlbytes: 存储一个无符号整数,用于存储 ziplist 的所用的字节数,(包括 zlbytes 字段本身的四个字节),当重新分配内容的时候,不需要遍历整个列表来计算内存大小。 -
zltail: 一个无符号整数,表示 ziplist 中最后一个元素的偏移字节数,这样可以方便的找到最后一个元素,从而可以以 O (1) 的复杂度在尾端进行 pop 和 push。 -
zllen:压缩列表包含的结点的个数,即entry的个数。
这里的zllen是占用16bit, 也就是说最多存储2^16-2个。但是ziplist超了2^16-2个也是可以表示的。那种情况就是16个1的时候,只需要从头遍历到尾就好了。 -
entry: 真正存放数据的数据项,每个数据项都有自己的内部结构。 -
zlend:ziplist的最后一个字节,值固定等于255,就是一个结束标记。
# entry 结构
entry 是由三部分构成的。
-
previous length(pre_entry_length): 表示前一个数据节点占用的总字节数,这个字段的用处是为了让 ziplist 能够从后向前遍历(从后一项的位置,只需向前偏移previous length个字节,就找到了前一项)。这个字段采用变长编码。 -
encoding(encoding&cur_entry_length):表示当前数据节点 content 的内容类型以及长度。也采用变长编码。 -
entry-data:表示当前节点存储的数据,entry-data的内容类型有整数类型和字节数组类型,且某些条件下entry-data的长度可能为0。
所以我们可以得出 ziplist 是一个这样的结构。

有时,encoding 也可以代表 entry 本身,就像小整数一样。

这里就是大体的了解下 ziplist 这种数据结构。
这里是一篇关于 ziplist 详细解读的文章。
# quicklistLZF
看完了比较神秘的 ziplist 结构,我们来看一个比较简单的 quicklist 的压缩节点的结构 quicklistLZF 。
1 | /** |
# 总结
list相关的命令。以及常见的应用场景。比如栈和队列等等。list其实是一种链表结构,但是不是一个普通的链表结构。list是由quicklist这种数据结构实现的。quicklist中的每个节点是quicklistNode, 而quicklist中zl指针,指向的是 一个ziplist或者quickListLZF。ziplist是一个比较神秘的数据结构,有5部分构成,是连续存储的,可以实现O(1)的尾端pop和push操作。
# 最后
期望与你一起遇见更好的自己
