Redis 3.0中的列表数据结构:基于gh_mirrors/re/redis-3.0-annotated的双向链表实现
在Redis 3.0中,列表(List)是一种常用的数据结构,它采用双向链表(Doubly Linked List)实现,支持高效的节点插入、删除和遍历操作。本文将深入解析Redis 3.0中列表数据结构的实现细节,基于gh_mirrors/re/redis-3.0-annotated项目的源代码展开分析。
双向链表的核心结构
Redis的双向链表实现主要包含三个核心数据结构:节点结构(listNode)、迭代器结构(listIter)和链表结构(list),定义在src/adlist.h头文件中。
节点结构(listNode)
typedef struct listNode {
struct listNode *prev; // 前置节点
struct listNode *next; // 后置节点
void *value; // 节点的值
} listNode;
每个节点包含指向前驱节点和后继节点的指针,形成双向链接。value字段使用void*类型,可以存储任意类型的数据,体现了Redis链表的通用性。
链表结构(list)
typedef struct list {
listNode *head; // 表头节点
listNode *tail; // 表尾节点
void *(*dup)(void *ptr); // 节点值复制函数
void (*free)(void *ptr); // 节点值释放函数
int (*match)(void *ptr, void *key); // 节点值对比函数
unsigned long len; // 链表所包含的节点数量
} list;
链表结构封装了链表的元数据,包括表头指针、表尾指针、节点数量以及三个函数指针(用于自定义节点值的复制、释放和对比操作)。len字段使得获取链表长度的操作可以在O(1)时间内完成。
迭代器结构(listIter)
typedef struct listIter {
listNode *next; // 当前迭代到的节点
int direction; // 迭代的方向
} listIter;
迭代器用于遍历链表,支持正向(从表头到表尾)和反向(从表尾到表头)两种遍历方向,由direction字段控制(AL_START_HEAD表示正向,AL_START_TAIL表示反向)。
双向链表的基本操作
Redis为双向链表提供了一系列完整的操作函数,实现了链表的创建、节点添加/删除、遍历、复制等功能,这些函数定义在src/adlist.c文件中。
链表的创建与释放
创建链表:listCreate函数初始化一个新的空链表,设置表头和表尾指针为NULL,长度为0,并初始化函数指针为NULL。
list *listCreate(void) {
struct list *list;
if ((list = zmalloc(sizeof(*list))) == NULL)
return NULL;
list->head = list->tail = NULL;
list->len = 0;
list->dup = NULL;
list->free = NULL;
list->match = NULL;
return list;
}
释放链表:listRelease函数遍历链表,释放所有节点及其值(如果设置了free函数),最后释放链表结构本身。
节点的添加与删除
添加节点:Redis提供了多种节点添加方式:
listAddNodeHead:在表头添加节点listAddNodeTail:在表尾添加节点listInsertNode:在指定节点的前后插入新节点
以listAddNodeHead为例,其实现如下:
list *listAddNodeHead(list *list, void *value) {
listNode *node;
if ((node = zmalloc(sizeof(*node))) == NULL)
return NULL;
node->value = value;
if (list->len == 0) {
list->head = list->tail = node;
node->prev = node->next = NULL;
} else {
node->prev = NULL;
node->next = list->head;
list->head->prev = node;
list->head = node;
}
list->len++;
return list;
}
删除节点:listDelNode函数从链表中移除指定节点,并根据free函数释放节点值。该操作通过调整相邻节点的指针实现,时间复杂度为O(1)。
链表的遍历
链表遍历通过迭代器实现,主要函数包括:
listGetIterator:创建迭代器listNext:获取下一个节点listReleaseIterator:释放迭代器listRewind/listRewindTail:重置迭代器到表头/表尾
遍历示例:
listIter *iter = listGetIterator(list, AL_START_HEAD);
listNode *node;
while ((node = listNext(iter)) != NULL) {
// 处理节点值
doSomethingWith(node->value);
}
listReleaseIterator(iter);
双向链表的应用场景
在Redis中,双向链表被广泛应用于多个功能模块,例如:
-
列表键(List):当列表键包含的元素较多,或元素为字符串且较长时,Redis会使用双向链表作为底层实现。
-
发布订阅(Pub/Sub):用于存储订阅频道的客户端列表。
-
慢查询日志:src/slowlog.c中使用双向链表存储慢查询日志条目,支持高效地添加新日志和遍历日志。
-
监视器(Monitor):用于存储所有连接的监视器客户端。
-
事件循环:src/ae.c中的文件事件和时间事件均通过链表管理。
双向链表的性能特点
Redis的双向链表实现具有以下性能特点:
- 高效的节点操作:表头/表尾节点的添加、删除操作时间复杂度为O(1)。
- 灵活的遍历方式:支持正向和反向遍历,迭代器模式避免了遍历过程中对链表结构的直接操作。
- 内存友好:使用Redis自定义的内存分配函数
zmalloc和zfree,便于内存使用统计和调试。 - 多态支持:通过函数指针
dup、free和match,使得链表可以存储任意类型的数据,并支持自定义的复制、释放和比较操作。
总结
Redis 3.0中的双向链表实现(src/adlist.h和src/adlist.c)是一个高效、灵活且通用的数据结构,为Redis的多种核心功能提供了基础支持。其设计充分体现了面向对象的思想,通过封装数据和操作,实现了代码的模块化和复用。理解这一实现不仅有助于深入掌握Redis的内部工作原理,也为高性能链表的设计提供了有益的参考。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



