链表 linkedlist
2023-3-23
| 2023-8-2
0  |  阅读时长 0 分钟
type
status
date
slug
summary
tags
category
icon
password
Property

 
链表提供了高效的节点重排能力, 以及顺序性的节点访问方式, 并且可以通过增删节点来灵活地调整链表的长度。因为Redis使用的C语言并没有内置这种数据结构, 所以Redis构建了自己的链表实现。
链表在Redis中的应用非常广泛, 比如列表键的底层实现之一就是链表: 当一个列表键包含了数量比较多的元素, 又或者列表中包含的元素都是比较长的字符串时, Redis就会使用链表作为列表键的底层实现。
 

队列和栈实现

Redis列表可以被当做栈、队列来使用,如果列表的元素是“右进左出”那就是队列模型;如果元素是“右进右出”那就是栈模型:
右进左出
右进右出
 
 

Redis 链表和链表节点的实现

每个链表节点使用一个 adlist.h/listNode 结构来表示:
多个 listNode 可以通过 prev 和 next 指针组成双端链表:
notion image
notion image
虽然仅仅使用多个 listNode 结构就可以组成链表, 但使用 adlist.h/list 来持有链表的话, 操作起来会更方便:
list 结构为链表提供了表头指针 head 、表尾指针 tail , 以及链表长度计数器 len , 而 dup 、 free 和 match 成员则是用于实现多态链表所需的类型特定函数:
  • dup 函数用于复制链表节点所保存的值
  • free 函数用于释放链表节点所保存的值
  • match 函数则用于对比链表节点所保存的值和另一个输入值是否相等
 
一个 list 结构和三个listNode 结构组成的链表:
notion image
notion image
Redis 的链表实现的特性可以总结如下:
  • 双端: 链表节点带有 prev 和 next 指针, 获取某个节点的前置节点和后置节点的复杂度都是   
  • 无环: 表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL , 对链表的访问以 NULL 为终点
  • 带表头指针和表尾指针: 通过 list 结构的 head 指针和 tail 指针, 程序获取链表的表头节点和表尾节点的复杂度为   
  • 带链表长度计数器: 程序使用 list 结构的 len 属性来对 list 持有的链表节点进行计数, 程序获取链表中节点数量的复杂度   
  • 多态: 链表节点使用 void* 指针来保存节点值, 并且可以通过 list 结构的 dup 、 free 、 match 三个属性为节点值设置类型特定函数, 所以链表可以用于保存各种不同类型的值
 
链表的缺陷也是有的:
  • 链表每个节点之间的内存都是不连续的,意味着无法很好利用 CPU 缓存。能很好利用 CPU 缓存的数据结构就是数组,因为数组的内存是连续的,这样就可以充分利用 CPU 缓存来加速访问。
  • 还有一点,保存一个链表节点的值都需要一个链表节点结构头的分配,内存开销较大
因此,Redis 3.0的List对象在数据量比较少的情况下,会采用「压缩列表」作为底层数据结构的实现,它的优势是节省内存空间,并且是内存紧凑型的数据结构。
不过,压缩列表存在性能问题,所以 Redis 在 3.2 版本设计了新的数据结构 quicklist,并将 List 对象的底层数据结构改由 quicklist 实现。
然后在 Redis 5.0 设计了新的数据结构 listpack,沿用了压缩列表紧凑型的内存布局,最终在最新的 Redis 版本,将 Hash 对象和 Zset 对象的底层数据结构实现之一的压缩列表,替换成由 listpack 实现。
 
 
链表和链表节点的 API
函数
作用
时间复杂度
listSetDupMethod
将给定的函数设置为链表的节点值复制函数
listGetDupMethod
返回链表当前正在使用的节点值复制函数
复制函数可以通过链表的 dup 属性直接获得,
listSetFreeMethod
将给定的函数设置为链表的节点值释放函数
listGetFree
返回链表当前正在使用的节点值释放函数
释放函数可以通过链表的 free 属性直接获得,
listSetMatchMethod
将给定的函数设置为链表的节点值对比函数
listGetMatchMethod
返回链表当前正在使用的节点值对比函数
对比函数可以通过链表的 match 属性直接获得,
listLength
返回链表的长度(包含了多少个节点)
链表长度可以通过链表的 len 属性直接获得,
listFirst
返回链表的表头节点
表头节点可以通过链表的 head 属性直接获得,
listLast
返回链表的表尾节点
表尾节点可以通过链表的 tail 属性直接获得,
listPrevNode
返回给定节点的前置节点
前置节点可以通过节点的 prev 属性直接获得,
listNextNode
返回给定节点的后置节点
后置节点可以通过节点的 next 属性直接获得,
listNodeValue
返回给定节点目前正在保存的值
节点值可以通过节点的 value 属性直接获得,
listCreate
创建一个不包含任何节点的新链表
listAddNodeHead
将一个包含给定值的新节点添加到给定链表的表头
listAddNodeTail
将一个包含给定值的新节点添加到给定链表的表尾
listInsertNode
将一个包含给定值的新节点添加到给定节点的之前或者之后
listSearchKey
查找并返回链表中包含给定值的节点
N 为链表长度
listIndex
返回链表在给定索引上的节点
N 为链表长度
listDelNode
从链表中删除给定节点
listRotate
将链表的表尾节点弹出,然后将被弹出的节点插入到链表的表头, 成为新的表头节点
listDup
复制一个给定链表的副本
N 为链表长度
listRelease
释放给定链表,以及链表中的所有节点
N 为链表长度
 
  • Redis
  • 动态字符串 SDS压缩列表 ziplist
    目录