Redis数据结构:List类型全面解析

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
云数据库 Tair(兼容Redis),内存型 2GB
简介: Redis数据结构——List类型全面解析:存储多个有序的字符串,列表中每个字符串成为元素 Eelement,最多可以存储 2^32-1 个元素。可对列表两端插入(push)和弹出(pop)、获取指定范围的元素列表等,常见命令。底层数据结构:3.2版本之前,底层采用**压缩链表ZipList**和**双向链表LinkedList**;3.2版本之后,底层数据结构为**快速链表QuickList**列表是一种比较灵活的数据结构,可以充当栈、队列、阻塞队列,在实际开发中有很多应用场景。

文章目录

一、List数据类型

  • 1.1 简介
  • 1.2 应用场景
  • 1.3 底层结构

二、数据结构

  • 2.1 压缩列表ZipList
  • 2.2 双向链表LinkedList(后续已废弃)
  • 2.3 快速链表QuickList

三、List常见命令

0-1.png

一、List数据类型

1.1 简介

详细介绍:Redis五种数据类型、String、List、Set、Hash、ZSet

Redis中的List类型与Java中的LinkedList类似,可以看做是一个双向链表结构,按插入顺序排序,你可以添加一个元素到头部(左边)或尾部(右边)。在 Redis 中,列表最多可以包含 2^32 - 1 个元素。既可以支持正向检索和也可以支持反向检索。特征也与LinkedList类似:

  • 有序
  • 元素可重复
  • 插入和删除快
  • 查询速度一般。

1.2 应用场景

根据 Redis 双向列表的特性,因此其也被用于异步队列的使用。实际开发中将需要延后处理的任务结构体序列化成字符串,放入 Redis 的队列中,另一个线程从这个列表中获取数据进行后续处理。

  • 消息队列:可以利用 List 的 push 和 pop 操作,实现生产者消费者模型。
  • 时间线、动态消息:比如微博的时间线,可以将最新的内容放在 List 的最前面。
  • 常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等

1.3 底层结构

  • 在3.2版本之前,Redis List底层采用压缩链表ZipList双向链表LinkedList来实现List。当元素数量小于512并且元素大小小于64字节时采用ZipList编码,超过则将自动采用LinkedList编码
  • 在3.2版本之后,Redis统一采用快速链表QuickList来实现List

二、数据结构

Redis的List结构类似一个双端链表,可以从首、尾操作列表中的元素:

  • 在Redis 3.2版本之前,Redis List底层采用压缩链表ZipList双向链表LinkedList来实现List。当元素数量小于512个并且元素大小小于64字节时采用ZipList编码,超过则将自动采用LinkedList编码。
  • 在3.2版本之后,Redis统一采用快速链表QuickList结构来实现List

QuickList结构如下:

2-1.png

2-2.png

在 Redis3.2 版本前,Redis 列表 List 使用两种数据结构作为底层实现:

  • 压缩列表 ZipList:插入元素过多或字符串太大,就需要调用 Realloc 扩展内存;
  • 双向链表 LinkedList:需附加指针 Prev 和 Next,较浪费空间,加重内存的碎片化

2.1 压缩列表ZipList

在 Redis3.2 版本前 压缩列表不仅是 List 的底层实现之一,同时也是 Hash、 ZSet 两种数据类型底层实现之一。

ZipList是一种特殊的“双端链表”(并非链表),由一系列特殊编码的连续内存块组成,像内存连续的数组。可以在任意一端进行压入/弹出操作,并且该操作的时间复杂度为O(1)。

2-3.png

压缩列表 底层数据结构:本质是一个数组,增加了列表长度、尾部偏移量、列表元素个数、以及列表结束标识,有利于快速寻找列表的首尾节点;但对于其他正常的元素,如元素2、元素3,只能一个个遍历,效率仍没有很高效。

当我们的 List 列表数据量比较少的时候,且存储的数据轻量的(如小整数值、短字符串)时候, Redis 就会通过压缩列表来进行底层实现。

2-4.png

2-5.png

属性 类型 长度 说明
zlbytes uint32_t 4字节 一个 4 字节的整数,表示整个压缩列表占用的字节数量,包括 <zlbytes> 自身的大小
zltail uint32_t 4字节 一个 4 字节的整数,记录压缩列表表尾节点距离压缩列表的起始地址有多少字节,通过这个偏移量,可以确定表尾节点的地址
zllen uint16_t 2字节 一个 2 字节的整数,表示压缩列表中的节点数量。最大值为UINT16_MAX(65534),如果超过这个数,此处会记录为65535,但节点的真实数量需要遍历整个压缩列表才能计算出
entry 列表节点 不定 压缩列表中的元素,每个元素都由一个或多个字节组成,节点的长度由节点保存的内容决定。每个元素的第一个字节(又称为"entry header")用于表示这个元素的长度以及编码方式
zlend uint8_t 1字节 一个字节,特殊值0xFF(十进制255),表示压缩列表的结束

2-6.png

注意:

  • 如果查找定位首个元素或最后1个元素,可以通过表头 “zlbytes”、“zltail_offset” 元素快速获取,复杂度是 O(1)。但是查找其他元素时,就没有这么高效了,只能逐个查找下去,比如 entryN 的复杂度就是 O(N)

  • ZipList虽然节省内存,但申请内存必须是连续空间,如果内存占用较多,申请效率较低。

2.2 双向链表LinkedList(后续已废弃)

LinkedList 是标准的双向链表,Node 节点包含 prev 和 next 指针,分别指向后继与前驱节点,因此从双向链表中的任意一个节点开始都可以很方便地访问其前驱与后继节点。

2-7.png

LinkedList 可以进行双向遍历;添加删除元素快 O(1),查找元素慢 O(n),高效实现了 LPUSH 、RPOP、RPOPLPUSH,但由于需要为每个节点分配额外的内存空间,所以会浪费一定的内存空间。这种编码方式适用于元素数量较多或者元素较大的场景。

LinkedList 结构为链表提供了表头指针 head、表尾指针 tail,以及节点数量计算 len。下图展示一个由 list 结构和三个 listNode 节点组成的链表:

2-8.png

Redis 的链表实现的特性可以总结如下:

  • 双端:链表节点带有 prev 和 next 指针,获取某个节点的前一节点和后一节点的复杂度都是 O(1);
  • 无环:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL,对链表的访问以 NULL 为终点;
  • 表头指针/表尾指针:通过 list 结构的 head 指针和 tail 指针,获取链表的表头节点和表尾节点的复杂度为 O(1);
  • 链表长度计数器:通过 list 结构的 len 属性来对 list 的链表节点进行计数,获取节点数量的复杂度为O(1);
  • 多态:链表节点使用 void* 指针来保存节点值,并通过 list 结构的 dup、free、match 三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。
  • 使用链表的附加空间相对太高,因为 64bit 系统中指针是 8 个字节,所以 prev 和 next 指针需要占据 16 个字节,且链表节点在内存中单独分配,会加剧内存的碎片化,影响内存管理效率

2.3 快速链表QuickList

QuickList底层 LinkedList + ZipList,可以从双端访问,内存占用较低,保含多个ZipList,存储上限高。其特点:

  • 是一个节点为ZipList的双端链表
  • 节点采用ZipList,解决了传统链表的内存占用问题
  • 控制了ZipList大小,解决连续内存空间申请效率问题
  • 中间节点可以压缩,进一步节省了内存

2-9.png

ZipList虽然节省内存,但申请内存必须是连续空间,如果内存占用较多,申请效率较低。

2-10.png

2-11.png

2-12.png

2-13.png

三、List常见命令

List的常见命令有

  • LPUSH key value [value ...] :向列表左侧插入一个或多个元素
  • LPOP key :移除并返回列表左侧的第一个元素,没有则返回nil
  • RPUSH key value [value ...] :向列表右侧插入一个或多个元素
  • RPOP key :移除并返回列表右侧的第一个元素
  • LRANGE key start stop:返回一段角标范围内的所有元素
  • BLPOP和BRPOP:与LPOP和RPOP类似,只不过在没有元素时等待指定时间,而不是直接返回nil
  • lindex key index:通过下标获得list当中的某一个值
  • llen key:获取list的长度

3-1.png

  • 如何利用List结构模拟一个栈?

    • 入口和出口在同一边
  • 如何利用List结构模拟一个队列?

    • 入口和出口在不同边
  • 如何利用List结构模拟一个阻塞队列?

    • 入口和出口在不同边
    • 出队时采用BLPOP或BRPOP
127.0.0.1:6379> lpush list1 18 20 Jenny       #向列表左侧插入一个或多个元素
(integer) 3
127.0.0.1:6379> lpush list1 Happy
(integer) 4
127.0.0.1:6379> lrange list1 0 2              #返回一段角标范围内的所有元素
1) "Happy"
2) "Jenny"
3) "20"
127.0.0.1:6379> rpush list1 Jack Health 16    #向列表右侧插入一个或多个元素
(integer) 7
127.0.0.1:6379> lrange list1 0 -1             #通过区间获取具体的值
1) "Happy"
2) "Jenny"
3) "20"
4) "18"
5) "Jack"
6) "Health"
7) "16"
127.0.0.1:6379> lpop list1                   #移除list的第一个元素:Happy
"Happy"
127.0.0.1:6379> rpop list1                   #移除list的最后一个元素:16
"16"
127.0.0.1:6379> lindex list1 2               #通过下标获得list当中的某一个值
"18"
127.0.0.1:6379> llen list1                   #获取list的长度
(integer) 5
127.0.0.1:6379> rpush list1 18 Jack
(integer) 7
127.0.0.1:6379> lrange list1 0 -1
1) "Jenny"
2) "20"
3) "18"
4) "Jack"
5) "Health"
6) "18"
7) "Jack"
127.0.0.1:6379> lrem list1 2 Jack           #移除list集合指定个数的value,移除2个值为Jack的,精确匹配
(integer) 2
127.0.0.1:6379> lrange list1 0 -1
1) "Jenny"
2) "20"
3) "18"
4) "Health"
5) "18"
127.0.0.1:6379> ltrim list1 3 4            #截取list集合中下标为3到下标为4之间的元素集合,并覆盖原来的list集合
OK
127.0.0.1:6379> lrange list1 0 -1
1) "Health"
2) "18"
127.0.0.1:6379> lset list 1 17
(error) ERR no such key
127.0.0.1:6379> lset list1 1 17           #更新list集合当中下标为1的值为17,如果下标1的值不存在,则报错
OK
127.0.0.1:6379> linsert list1 AFTER Health 20    #将某个具体的值插入到某一个具体元素(默认第一个)的前面或者后面
(integer) 3
127.0.0.1:6379> linsert list1 BEFORE Health Jenny
(integer) 4
127.0.0.1:6379> lrange list1 0 -1
1) "Jenny"
2) "Health"
3) "20"
4) "17"

3-2.png

参考 Redis基础(超详解)一 :Redis定义、SQL与NoSQL区别、Redis常用命令、Redis五种数据类型、String、List、Set、Hash、ZSet;Redis的Java客户端Redis数据结构:List类型全面解析

相关文章
|
10天前
|
开发者
除了交集运算,Set 类型还可以用于哪些数据结构的操作?
【10月更文挑战第30天】`Set`类型在数据结构操作方面提供了丰富的功能和便利,能够帮助开发者更高效地处理各种数据集合相关的任务,提高代码的简洁性和性能。
|
22天前
|
存储 消息中间件 NoSQL
Redis 数据结构与对象
【10月更文挑战第15天】在实际应用中,需要根据具体的业务需求和数据特点来选择合适的数据结构,并合理地设计数据模型,以充分发挥 Redis 的优势。
54 8
|
22天前
|
存储 NoSQL Java
介绍下Redis 的基础数据结构
本文介绍了Redis的基础数据结构,包括动态字符串(SDS)、链表和字典。SDS是Redis自实现的动态字符串,避免了C语言字符串的不足;链表实现了双向链表,提供了高效的操作;字典则类似于Java的HashMap,采用数组加链表的方式存储数据,并支持渐进式rehash,确保高并发下的性能。
介绍下Redis 的基础数据结构
|
26天前
|
NoSQL 关系型数据库 MySQL
Redis 列表(List)
10月更文挑战第16天
16 2
|
17天前
|
存储 NoSQL 关系型数据库
Redis的ZSet底层数据结构,ZSet类型全面解析
Redis的ZSet底层数据结构,ZSet类型全面解析;应用场景、底层结构、常用命令;压缩列表ZipList、跳表SkipList;B+树与跳表对比,MySQL为什么使用B+树;ZSet为什么用跳表,而不是B+树、红黑树、二叉树
|
18天前
|
存储 NoSQL Redis
Redis常见面试题:ZSet底层数据结构,SDS、压缩列表ZipList、跳表SkipList
String类型底层数据结构,List类型全面解析,ZSet底层数据结构;简单动态字符串SDS、压缩列表ZipList、哈希表、跳表SkipList、整数数组IntSet
|
2天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
14 2
|
1月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
66 0
|
1月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
52 0
|
1月前
|
存储 Java C++
Collection-PriorityQueue源码解析
Collection-PriorityQueue源码解析
59 0

相关产品

  • 云数据库 Tair(兼容 Redis)
  • 推荐镜像

    更多