1. 列表简介
列表(list)类型是用来存储多个有序的字符串,如下图所示,a、b、c、d、e五个元素从左到右组成了一个有序的列表,列表中的每个字符串称为元素(element),一个列表最多可以存储2^32-1
个元素。在Redis中,可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用场景。

列表的特点:
- 有序
- 可以重复
- 右边进左边出或者左边进右边出,则列表可以充当队列
- 左边进左边出或者右边进右边出,则列表可以充当栈
2. 常用命令
- 添加元素
# 右边插入元素
node02:6379> rpush list a b c
(integer) 3
# 从左到右查看所有元素
node02:6379> lrange list 0 -1
1) "a"
2) "b"
3) "c"
# 左边插入元素
node02:6379> lpush list 1 2 3
(integer) 6
node02:6379> lrange list 0 -1
1) "3"
2) "2"
3) "1"
4) "a"
5) "b"
6) "c"
# 在某个元素之前插入元素
# LINSERT key BEFORE|AFTER pivot value
# 在 a 之前插入 0
node02:6379> linsert list before a 0
(integer) 7
node02:6379> lrange list 0 -1
1) "3"
2) "2"
3) "1"
4) "0"
5) "a"
6) "b"
7) "c"
- 查找元素
# 查找下标范围内的元素
# LRANGE key start stop
node02:6379> lrange list 0 3
1) "3"
2) "2"
3) "1"
4) "0"
# 查找指定下标的元素
node02:6379> lindex list 4
"a"
# 获取列表长度
node02:6379> llen list
(integer) 7
- 删除元素
- 左侧弹出
node02:6379> lrange list 0 -1
1) "2"
2) "1"
3) "0"
4) "a"
5) "b"
6) "c"
# 右侧弹出
node02:6379> rpop list
"c"
node02:6379> lrange list 0 -1
1) "2"
2) "1"
3) "0"
4) "a"
5) "b"
# 按照元素值删除
# LREM key count value
# count = 0,删除全部指定的元素
# count < 0, 从右到左,删除|count|个指定元素
# count > 0, 从左到右,删除count个指定元素
node02:6379> rpush list2 a a a a a b b b b b a a b b a
(integer) 15
node02:6379> lrem list2 3 a
(integer) 3
node02:6379> lrange list2 0 -1
1) "a"
2) "a"
3) "b"
4) "b"
5) "b"
6) "b"
7) "b"
8) "a"
9) "a"
10) "b"
11) "b"
12) "a"
node02:6379> lrem list2 -5 b
(integer) 5
node02:6379> lrange list2 0 -1
1) "a"
2) "a"
3) "b"
4) "b"
5) "a"
6) "a"
7) "a"
node02:6379> lrem list2 0 a
(integer) 5
node02:6379> lrange list2 0 -1
1) "b"
2) "b"
# 按照元素下标保留,其余删除
# LTRIM key start stop
node02:6379> rpush list3 0 1 2 3 4 5 6
(integer) 7
# 保留下标 1-3 的元素,其余删除
node02:6379> ltrim list3 1 3
OK
node02:6379> lrange list3 0 -1
1) "1"
2) "2"
3) "3"
- 修改元素
# LSET key index value
node02:6379> lrange list3 0 -1
1) "1"
2) "2"
3) "3"
node02:6379> lset list3 0 hello
OK
node02:6379> lrange list3 0 -1
1) "hello"
2) "2"
3) "3"
- 阻塞式弹出
如果列表不存在或者列表为空,那么弹出操作会在设置的时间后返回结果,如果设置时间为0,那么除非成功弹出元素,否则一直阻塞。
# BRPOP key [key ...] timeout
node02:6379> exists list4
(integer) 0
node02:6379> brpop list4 0
# 阻塞....
# 在另一个客户端设置push一个值进去
node02:6379> rpush list4 tom
(integer) 1
# 弹出后返回结果,阻塞了105s
node02:6379> brpop list4 0
1) "list4"
2) "tom"
(105.86s)
# list5 不存在
node02:6379> exists list5
(integer) 0
# 阻塞 3s 后返回结果
node02:6379> brpop list5 3
(nil)
(3.10s)
# list5 中有元素
node02:6379> rpush list5 tom
(integer) 1
# 则立即弹出
node02:6379> brpop list5 5
1) "list5"
2) "tom"
# blpop 与 brpop 的用法相同
阻塞式弹出的注意事项:
- 如果目标列表有多个,且每个列表中都没有元素,那么一直阻塞或者到设置的时间后返回
- 在阻塞期间,如果在任意一个目标列表中加入了元素,导致该列表成功弹出元素,那么就不会再阻塞,而是直接返回结果。并不是需要等到全部目标列表都弹出了一个元素才返回结果。
- 如果多个客户端对同一个列表进行阻塞式弹出,如果多个客户端都阻塞了,那么首先成功弹出的客户端首先收到返回结果,其他的客户端继续阻塞。
3. 内部编码
- ziplist
ziplist 是一块连续的内存空间,元素之间紧挨着存储,没有任何冗余空隙。这样设计的优点是可以节约内存,并且查询元素的速度会更快,但是插入元素的性能不好,原理同 Java 的 ArrayList。 - linkedlist
linkedlist 相当于 Java 的 LinkedList,优点是插入性能好,只需要修改两个指针即可,缺点是占内存,因为存储了额外的指针等信息,同时查询性能也不好,需要从头到尾遍历。 - quicklist
redis-3.2版本提供了 quicklist 实现方式,结合了 ziplist 和 linkedlist 的优点,原理如下图所示:

quicklist 是一个 linkedlist,这个 linkedlist 是 ziplist 组成的双向链表。
首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,它将所有的元素紧挨着一起存储,分配的是一块连续的内存。
当数据量比较多的时候才会改成 quicklist 。因为普通的链表需要的附加指针空间太大,会比较浪费空间,而且会加重内存的碎片化。比如这个列表里存的只是 int 类型的数据,结构上还需要两个额外的指针 prev(前一个节点) 和 next(后一个节点)。
所以 redis 将链表和 ziplist 结合起来组成了 quicklist 也就是将多个 ziplist 使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。
相关的参数:
- quicklist 中每个 ziplist 的大小
list-max-ziplist-size -2
这个值如果设置为正数,代表的是 quicklist 的每个 ziplist 中最多可以存储元素的个数。
如果设置负数,则代表用字节数而不是元素个数来限定每个 ziplist 的大小,具体信息如下:
- -5:每个 ziplist 最大为 64kb,不建议使用
- -4:每个 ziplist 最大为 32kb,不建议使用
- -3:每个 ziplist 最大为 16kb,不建议使用
- -2:默认值,每个 ziplist 最大为 8kb,性能最好
- -1:每个 ziplist 最大为 4kb,性能最好
- 压缩深度
list-compress-depth 0
quicklist 默认的压缩深度是 0,也就是不压缩。
压缩深度为 1 代表:quicklist 的首尾两个 ziplist 不压缩,其余的 ziplist 压缩。
压缩深度为 2 代表:quicklist 的首尾的前两个 ziplist 不压缩,其余的 ziplist 压缩。
以此类推。
压缩会使得内存占用变小,但是对 list 的读写请求的性能会下降,因为增加了解压缩的开销。因此这个值保持默认即可,如果确实想要节约内存,可以把压缩深度设置为 1。算是一种中和的取值。
网友评论