type
status
date
slug
summary
tags
category
icon
password
Property
链表提供了高效的节点重排能力, 以及顺序性的节点访问方式, 并且可以通过增删节点来灵活地调整链表的长度。因为
Redis
使用的C
语言并没有内置这种数据结构, 所以Redis
构建了自己的链表实现。链表在
Redis
中的应用非常广泛, 比如列表键的底层实现之一就是链表: 当一个列表键包含了数量比较多的元素, 又或者列表中包含的元素都是比较长的字符串时, Redis
就会使用链表作为列表键的底层实现。队列和栈实现
Redis
列表可以被当做栈、队列来使用,如果列表的元素是“右进左出”那就是队列模型;如果元素是“右进右出”那就是栈模型:右进左出
右进右出
Redis 链表和链表节点的实现
每个链表节点使用一个
adlist.h/listNode
结构来表示:多个
listNode
可以通过 prev
和 next
指针组成双端链表:虽然仅仅使用多个
listNode
结构就可以组成链表, 但使用 adlist.h/list
来持有链表的话, 操作起来会更方便:list
结构为链表提供了表头指针 head
、表尾指针 tail
, 以及链表长度计数器 len
, 而 dup
、 free
和 match
成员则是用于实现多态链表所需的类型特定函数:dup
函数用于复制链表节点所保存的值
free
函数用于释放链表节点所保存的值
match
函数则用于对比链表节点所保存的值和另一个输入值是否相等
一个
list
结构和三个listNode
结构组成的链表: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 为链表长度 |