1楔子
双向链表为redis列表类型的实现方法之一,列表类型实现除了用到双向链表,还有压缩列表。因为双向链表占用内存较多,所以redis优先采用压缩列表来实现自己的列表类型。压缩列表后续分析,先看看双向链表的代码。
双向链表作为一个基本的数据结构,在很多书上都会有描述了,下面就一个个函数看看redis的双向链表的实现方法。文件参见src/adlist.c和src/adlist.h。关于这个命名是双向链表还是双端链表在huangz1990博客上还有争议,哈哈,我就先不管这些概念上的问题了。很多内容参考:http://www.redisbook.com/en/latest/internal-datastruct/adlist.html
2双向链表定义
先看下adlist.h中双向链表的节点定义:
-
-
-
- typedef struct listNode {
-
- struct listNode *prev;
-
- struct listNode *next;
-
- void *value;
- } listNode;
节点listNode定义很常规,一个前驱节点指针,一个后继节点指针,一个值的指针,值的类型为void *,可以存储各种类型。
双向链表本身的定义如下:
-
-
-
- typedef struct list {
-
- listNode *head;
-
- listNode *tail;
-
- unsigned long len;
-
-
- void *(*dup)(void *ptr);
-
- void (*free)(void *ptr);
-
- int (*match)(void *ptr, void *key);
- } list;
即定义了链表头指针,尾指针,长度以及复制,释放和匹配的函数指针。
实际的链表结构图如下:
图1 redis双向链表结构示意图(取自huangz1990博客)
正是因为有表头、表尾指针以及链表长度记录,所以redis在表头和表尾插入数据都非常方便,时间复杂度为O(1),这也是lpush,lpop,llen等命令效率较高的原因。
3基本函数
1)listCreate:创建列表
-
-
-
-
-
-
- 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;
- }
创建链表即是分配struct list的大小的内存,并初始化链表节点数目为0、链表头尾指针和基本函数指针为NULL。
2)listRelease:释放列表
-
-
-
-
-
- void listRelease(list *list)
- {
- unsigned long len;
- listNode *current, *next;
-
- current = list->head;
- len = list->len;
- while(len--) {
- next = current->next;
-
- if (list->free) list->free(current->value);
-
- zfree(current);
- current = next;
- }
- zfree(list);
- }
释放列表即从表头根据链表节点数目遍历,并依次释放各个节点。如果链表定义了free方法,则先释放节点值占用的内存。最后,释放链表本身的管理结构的内存。
3)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;
- }
添加节点的代码很简洁。首先分配内存空间,然后设置节点值为指定的值。然后建立链接关系:如果是第一个节点,直接设置表头和表尾指针为新加入节点;否则,设置新加入节点与原来的头节点的链接关系,并设定头节点为新加入的节点。最后更新链表节点数目,并返回传入的链表。
4)listAddNodeTail:在表尾添加节点
-
-
-
-
-
-
-
-
- list *listAddNodeTail(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 = list->tail;
- node->next = NULL;
- list->tail->next = node;
- list->tail = node;
- }
-
- list->len++;
-
- return list;
- }
与表头添加节点类似,只是添加在了链表尾部而已。
5)listInsertNode:在指定节点前后插入节点
-
-
-
-
-
-
- list *listInsertNode(list *list, listNode *old_node, void *value, int after) {
- listNode *node;
-
- if ((node = zmalloc(sizeof(*node))) == NULL)
- return NULL;
-
- node->value = value;
-
- if (after) {
-
- node->prev = old_node;
- node->next = old_node->next;
-
- if (list->tail == old_node) {
- list->tail = node;
- }
- } else {
-
- node->next = old_node;
- node->prev = old_node->prev;
-
- if (list->head == old_node) {
- list->head = node;
- }
- }
-
- if (node->prev != NULL) {
- node->prev->next = node;
- }
- if (node->next != NULL) {
- node->next->prev = node;
- }
-
-
- list->len++;
-
- return list;
- }
这里需要注意的是处理表头和表尾节点,以及后面更新前置节点和后继节点指针部分。
6)listDelNode:删除链表中指定节点
-
-
-
-
-
-
- void listDelNode(list *list, listNode *node)
- {
-
- if (node->prev)
- node->prev->next = node->next;
- else
- list->head = node->next;
-
-
- if (node->next)
- node->next->prev = node->prev;
- else
- list->tail = node->prev;
-
-
- if (list->free) list->free(node->value);
-
-
- zfree(node);
-
-
- list->len--;
- }
4 迭代器及相关函数
迭代器定义
Redis为双向链表还实现了一个迭代器,可以从两个方向迭代链表。迭代器定义如下:
-
-
-
- typedef struct listIter {
-
- listNode *next;
-
- int direction;
- } listIter;
direction如果设定为AL_START_HEAD(0),则从表头到表尾迭代。
direction如果设定为AL_START_TAIL(1),则从表尾向表头迭代。
相关函数
1)listGetIterator:迭代器创建
-
-
-
-
-
- listIter *listGetIterator(list *list, int direction)
- {
- listIter *iter;
-
- if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL;
-
-
- if (direction == AL_START_HEAD)
- iter->next = list->head;
- else
- iter->next = list->tail;
-
-
- iter->direction = direction;
-
- return iter;
- }
2)迭代器释放,迭代指针调整函数
-
-
-
-
-
- void listReleaseIterator(listIter *iter) {
- zfree(iter);
- }
-
-
-
-
-
-
- void listRewind(list *list, listIter *li) {
- li->next = list->head;
- li->direction = AL_START_HEAD;
- }
-
-
-
-
-
-
- void listRewindTail(list *list, listIter *li) {
- li->next = list->tail;
- li->direction = AL_START_TAIL;
- }
3)listIterNext:迭代函数
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- listNode *listNext(listIter *iter)
- {
- listNode *current = iter->next;
-
- if (current != NULL) {
-
- if (iter->direction == AL_START_HEAD)
- iter->next = current->next;
- else
- iter->next = current->prev;
- }
-
- return current;
- }
迭代器在迭代过程中,根据迭代方向,每次更新next指针即可。