Redis List,你真的了解吗?

2023-03-24 00:00:00 数据 元素 列表 对象 编码

文章来源:https://mp.weixin.qq.com/s/JQ-ph0rebFeOBQ54BxjGTw

我们上周已经经过了String的面试,相信大家对String也有了基本的认知。

我们知道,一个字符串可以用String表示,那么如果有成群结队的String,又用什么数据结构来表示呢?

今天我们就来认识一下这个数据结构——List。

1.是什么?

Redis List是一组连接起来的字符串集合。

1.1 有什么限制

List大元素个数是 2^32 - 1 (4,294,967,295)。

2.适用场景

List作为一个列表存储,属于比较底层的数据结构,可以使用的场景非常多,比如存储一批任务数据,存储一批消息等。

3. 常用操作

常用操作还是聚焦于创建、查询、更新和删除。

创建即产生一个List对象,一般用LPUSH、RPUSH,分别对应从左侧进队列和右侧进队列。

对于查询,可以用LRANGE来进行范围查询,可以用LLEN查询元素个数。

更新的话,对列表对象而言就是追加元素、删除元素等操作,涉及LPUSH, LPOP, RPUSH, RPOP等操作。

删除就是针对List对象本身的生成和销毁,用DEL命令。

下面是一些常用命令的例子。

3.1 写操作

3.1.1 LPUSH

语法:LPUSH key value [value ...]

功能:从头部增加元素,返回值为List中元素的总数。

127.0.0.1:6379> LPUSH listniuniu s1 s2 s3(integer) 3
127.0.0.1:6379> LPUSH listniuniu s4(integer) 4
s4是第二个命令插入的,插入之后它就在List头部,此时List结构如下:

3.1.2 RPUSH

语法:RPUSH key value [value ...]

功能:从尾部增加元素,返回值为List中元素的总数。

127.0.0.1:6379> RPUSH listniuniu s5(integer) 5
s5是用RPUSH命令插入的,插入之后它就在List尾部,此时List结构如下:

3.1.3  LPOP

语法:LPOP key

功能:移出并获取列表的个元素。

127.0.0.1:6379> LPOP listniuniu"s4"
s4从List中移除,此时List结构如下:

3.1.4 RPOP

语法:RPOP key
功能:移出并获取列表的后一个元素。
127.0.0.1:6379> RPOP listniuniu"s5"
s5从List中移除,此时List结构如下:

3.1.5 DEL

语法:DEL key [key ...]

功能:删除对象,返回值为删除成功了几行。

127.0.0.1:6379> DEL listniuniu(integer) 1


3.2 读操作

我们使用如下命令,构造集合用于读操作的学习:

127.0.0.1:6379> DEL listniuniu(integer) 1127.0.0.1:6379> LPUSH listniuniu s1 s2 s3(integer) 3
执行完成之后,数据如下:

3.2.1 LLEN

语法:LLEN key

功能:查看List的长度,即List中元素的总数。

127.0.0.1:6379> LLEN listniuniu(integer) 3


3.2.2 LRANGE

语法:LRANGE key start stop

功能:查看start到stop为角标的元素。

127.0.0.1:6379> LRANGE listniuniu 0 11) "s3"2) "s2"

start、stop如果为负数,表示倒数第几个元素:

127.0.0.1:6379> LRANGE listniuniu -2 -11) "s2"2) "s1"


4.底层实现

4.1 编码方式

3.2版本之前,List对象有两种编码方式,一种ZIPLIST,另一种是LINKEDLIST。
当满足如下条件时,用ZIPLIST编码:
1.列表对象保存的所有字符串对象长度都小于64字节;
2.列表对象元素个数少于512个。
ZIPLIST底层用压缩列表实现,这里我们假设列表中包含"hello"、"niuniu"、 "mart"三个元素,ZIPLIST编码示意如下:
可以看到,"hello"、“niuniu"、"mart"都挨在一起,正如其名字一样,ZIPLIST内存排列得很紧凑,可以有效节约内存空间。
其它几个数据字段我们这里可以先不关心,下一节会详细讲解ZIPLIST的结构,目前我们只需要能宏观理解压缩列表的数据是紧凑相连的即可。
如果不满足ZIPLIST编码的条件,则使用LINKEDLIST编码。为了便于描述,我们还是假设列表中包含"hello"、"niuniu"、"mart"三个元素,如果用LINKEDLIST编码,则是几个String对象的链接结构,结构示意图如下:
可以看到,"hello"、"niuniu"、"mart" 这几个数据是以链表的形式连接在一起,实际上删除更为灵活,但是内存不如ZIPLIST紧凑,所以只有在列表个数或节点数据长度比较大的时候,才会使用LINKEDLIST编码,以加快处理性能,一定程度上牺牲了内存。

4.2 QUICKLIST横空出世

上面的分析有说到,ZIPLIST是为了在数据较少时节约内存,LINKEDLIST是为了数据多时方便查询。
但如果节点非常多的情况,LINKEDLIST链表的节点就很多,会占用不少的内存。
这种情况有没有办法优化呢?
3.2版本就引入了QUICKLIST。QUICKLIST其实就是ZIPLIST和LINKEDLIST的结合体。
LINKEDLIST原来是单个节点,只能存一个数据,现在单个节点存的是一个ZIPLIST,即多个数据。
这种方案其实是用ZIPLIST、LINKEDLIST综合的结构,取代二者本身。
当数据较少的时候,QUICKLIST的节点就只有一个,此时其实相当于就是一个ZIPLIST;
当数据很多的时候,则同时利用了ZIPLIST和LINKEDLIST的优势。

4.3 ZIPLIST优化

ZIPLIST本身存在一个连锁更新的问题,所以Redis 7.0之后,使用了LISTPACK的编码模式取代了ZIPLIST,而他们其实本质都是一种压缩的列表,所以其实可以统一叫做压缩列表。
ZIPLIST的细节,以及LISTPACK是怎么优化的,我们将在下一节介绍。

总结

List是Redis中的列表实现,它是一个可双端操作的对象,编码的底层数据结构涉及ZIPLIST和LINKEDLIST。

相关文章