Doubly Linked List
链表是线性表的一种实现,在 Redis 中也是列表类型的底层实现之一。C 标准库没有内建的链表类型,所以 Redis 自己实现了一个泛型双向链表。由于链表的操作和结构有广泛的认识基础,本文仅做简单说明。
概述
- adlist.h
- adlist.c
Redis 中的链表是一个带头节点的双向链表,同时头节点还有指向尾部的指针域。结构如图示:
同时 Redis 中还定义了一个迭代器,指向链表中的一个节点并且标识出来迭代方向。定义如下:
1
2
3
4
5
6
7
8
9
10
//adlist.h:42
typedef struct listIter {
listNode *next;
int direction;
} listIter;
//adlist.h:92
/* Directions for iterators */
#define AL_START_HEAD 0
#define AL_START_TAIL 1
有结构可知,除了双向链表固有的特点之外,Redis 的双向链表还有以下特点:
- 有固定的头节点;
- 头节点保存链表的长度;
- 无环且头节点有指向头节点和尾节点的域;
- 提供了三个函数指针,用于链表节点的复制、释放和比较,由调用方根据存储的数据类型自行实现。
操作
内存管理
Reids 链表主要依赖底层内存管理实现,具体分析参见章节Redis 内存管理。比较特别的是,链表头存储了节点内存释放的函数指针,需要调用方自行实现。
宏函数
链表操作的宏函数主要以取结构中某些字段为主,逻辑简单,仅贴上源码,顾名思义即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//adlist.h:56
/* Functions implemented as macros */
#define listLength(l) ((l)->len)
#define listFirst(l) ((l)->head)
#define listLast(l) ((l)->tail)
#define listPrevNode(n) ((n)->prev)
#define listNextNode(n) ((n)->next)
#define listNodeValue(n) ((n)->value)
#define listSetDupMethod(l,m) ((l)->dup = (m))
#define listSetFreeMethod(l,m) ((l)->free = (m))
#define listSetMatchMethod(l,m) ((l)->match = (m))
#define listGetDupMethod(l) ((l)->dup)
#define listGetFree(l) ((l)->free)
#define listGetMatchMethod(l) ((l)->match)
链表的 CRUD
创建adlist.c:41#listCreate(void)->list *
创建链表实质上是初始化一个空的头节点,申请头节点内存并将所有域置为NULL
,长度设置为0。特别的,内存申请失败后返回的头节点地址为NULL
。
复制adlist.c:250#listDup(list *)->list *
复制链表依赖链表创建和链表迭代器。特别的,链表节点的复制依赖头节点的dup
域,可以实现多态下的节点值深拷贝。
节点插入
链表增加节点分为头插、尾插和给定节点前后插入值,具体逻辑不赘述,仅列出实现的函数。
adlist.c:88#listAddNodeHead(list *, void *)->list *
adlist.c:114#listAddNodeTail(list *, void *)->list *
adlist.c:134#listInsertNode(list *, listNode *, void *, int)->list *
- 本函数做简单额外说明。形参的最后一个用作 bool 值,表示插入方向。
上述函数均以返回值作为链表头节点,原头节点不应有任何读写操作。如果返回值为NULL
,则表明内存操作返回为空,应该以操作失败处理。
节点的删除与内容清空
逻辑简单不赘述,仅列出函数。特别的,如果头节点free
域有值,则可以实现调用方自行析构节点。
adlist.c:167#listDelNode(list *, listNode *)
adlist.c:56#listEmpty(list *)
遍历
Redis 的双向链表提供了迭代器形式的遍历,支持从头节点或尾节点的双向遍历。
链表的释放adlist.c:76#listRelease(list *)
先清空链表(即释放所有节点),再释放头节点。
API汇总
注:调用返回值类型为list
的函数后,应使用返回值作为新链表使用,原链表不应再有任何读写操作。
注2:复杂度中的问题规模 N 表示链表的节点数量
函数声明 | 源码位置 | 功能简要说明 | 时间复杂度 |
---|---|---|---|
list *listCreate(void) |
adlist.c:41 | 初始化空的头节点 | O(1) |
void listRelease(list *list) |
adlist.c:76 | 释放链表所有节点(包括头节点) | O(N) |
void listEmpty(list *list) |
adlist.c:56 | 释放链表所有节点(头节点除外) | O(N) |
list *listAddNodeHead(list *list, void *value) |
adlist.c:88 | 头插节点 | O(1) |
list *listAddNodeTail(list *list, void *value) |
adlist.c:114 | 尾插节点 | O(1) |
list *listInsertNode(list *list, listNode *old_node, void *value, int after) |
adlist.c:134 | 指定节点(未校验节点是否属于链表)前或后插入 | O(1) |
void listDelNode(list *list, listNode *node) |
adlist.c:167 | 删除指定节点(未校验节点是否属于链表) | O(1) |
listIter *listGetIterator(list *list, int direction) |
adlist.c:186 | 指定方向获取链表迭代器 | O(1) |
listNode *listNext(listIter *iter) |
adlist.c:229 | 获取迭代器下一个指向的节点(空值表示迭代结束) | O(1) |
void listReleaseIterator(listIter *iter) |
adlist.c:200 | 释放迭代器 | O(1) |
list *listDup(list *orig) |
adlist.c:250 | 复制链表 | O(N) |
listNode *listSearchKey(list *list, void *key) |
adlist.c:290 | 列表中查找指定值(值比较可以由调用方自定义)的节点,未找到时返回空 | O(N) |
listNode *listIndex(list *list, long index) |
adlist.c:315 | 指定索引(起点为0,负数表示从尾节点向前)查找链表节点,未找到时返回空 | O(N) |
void listRewind(list *list, listIter *li) |
adlist.c:205 | 初始化正向迭代器 | O(1) |
void listRewindTail(list *list, listIter *li) |
adlist.c:210 | 初始化逆向迭代器 | O(1) |
void listRotateTailToHead(list *list) |
adlist.c:330 | 尾节点变头节点 | O(1) |
void listRotateHeadToTail(list *list) |
adlist.c:345 | 头节点变尾节点 | O(1) |
void listJoin(list *l, list *o) |
adlist.c:361 | 链表追加(操作后 o 依然是有效的头节点,只是节点为空长度为0) | O(1) |