回答
Redis 中的 List 数据结构是一个双向链表,用于存储一个序列的数据,它类似于 Java 中的数组或列表,其底层实现分为两个版本:
- 3.2 版本以前使用
linkedlist
+ziplist
- 当列表中元素的⻓度较⼩或者数量较少时,通常采⽤
zipList
来存储。原因是因为zipList
是一个紧凑的数据结构,能够有效地减少内存占用。但是,在列表中元素较多或者元素较大时,zipList
的性能会下降,因为在列表的头部或尾部添加或删除元素时,可能需要重新分配并复制整个ziplist
。所以,zipList
非常适合少量的小数据存储。同时 zipList 还有一个“连锁更新”的问题,也会严重影响ziplist
的性能。 - 当列表元素大于 512 或者元素的长度大于 64 字节时,List 底层由
zipList
转换为linkedlist
。linkedlist
是一个双向链表,能够快速支持列表的删除和插入操作,但是内存利用率较低,因为它需要额外的内存空间来维护链表结构。
- 当列表中元素的⻓度较⼩或者数量较少时,通常采⽤
- 3.2 版本以后使用
quicklist
- 从 3.2 版本开始后,List 的底层实现采用
quicklist
。quicklist
是将zipList
作为节点嵌入linkedList
的节点中,它结合了两者的优点,也具备两者的优点。具体来说,quicklist
是由多个ziplist
节点组成的linkedList
链表,每个ziplist
节点可以存储多个列表元素。
- 从 3.2 版本开始后,List 的底层实现采用
扩展
List 介绍
List 的 Redis 中的 5 种主要数据结构之一,它是一种序列集合,可以存储一个有序的字符串列表,顺序是插入的顺序。我们可以使用相关命令添加一个字符串元素到 List 的头部(左边)或者尾部。
**List 的最大长度为 2^31 - 1,即每个 List 支持超过 40 亿个元素。**主要特点如下:
- 有序性:List 中的元素按照插入顺序排序,可以在列表的头部或尾部添加元素。
- 双向链表:List 内部通过双向链表实现,使得在列表的两端操作(如插入和删除)都非常快,时间复杂度为 O(1)。
- 元素重复性:List 允许重复的元素。
- 元素访问:List 支持通过索引访问元素(如 LINDEX 命令),但这是通过遍历实现的,因此其时间复杂度为 O(N)。若 List 元素较多,则访问效率较低
List 常用的命令如下:
LPUSH key value1 [value2]
:将一个或多个元素插入到列表头部。如果 key 不存在,一个空列表会被创建并执行LPUSH
操作。返回值是操作后列表的长度。RPUSH key value1 [value2]
:将一个或多个值插入到列表尾部。类似于 LPUSHLPOP key
:移除并返回列表的第一个元素。如果列表为空,返回 nil。RPOP key
:移除并返回列表的最后一个元素。如果列表为空,返回 nil。LLEN key
:返回列表的长度,如果 key 不存在 返回 0。LINDEX key index
:获取列表在 index 位置的元素。index 是基于 0 的,也可以是负数,-1 表示最后一个元素,-2 表示倒数第二个元素等。LSET key index value
:将列表的 index 位置的值设置为 value。如果 index 超出范围,操作会返回一个错误。LRANGE key start stop
:获取列表指定范围内的元素。start 和 stop 都是基于 0 的索引,可以使用负数索引指定位置。LREM key count value
:根据参数 count 的值,移除与参数 value 相等的元素。count 的值可以是以下几种:- count > 0:从头到尾,移除最多 count 个 value 相等的元素。
- count < 0:从尾到头,移除最多 -count 个 value 相等的元素。
- count = 0:移除所有 value 相等的元素。
LTRIM key start stop
:对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
下面是 List 常用命令的演示:
需要注意的是,List 设置过期时间,只能给整个 List 设置过期时间,不能单独给某一个元素设置。
List 的底层实现
List 的底层实现有三种:zipList
、linkedList
和 quickList
。他们的使用情况如下:
- 当 List 存储的元素较少且每个元素的大小也较小时,Redis 会选择使用
zipList
来存储数据,以节省内存空间。 - 当列表元素大于 512 或者元素的长度大于 64 字节时,Redis 则转换为使用
linkedList
来存储数据,以优化操作的性能。
zipList
当 List 中的元素比较少或者每个元素的大小也小时,Redis 选择 zipList
来存储数据。zipList
通过紧凑的内存布局存储一系列的 entry,每个 entry 可以代表一个字符串或者整数。
zipList
的内部结构主要分为三个部分:表头(header)、条目(entries)和表尾(end),如下图: