Redis源码学习3-基本数据结构之双向链表

[-]

  1. 楔子
  2. 双向链表定义
  3. 基本函数
    1. listCreate创建列表
    2. listRelease释放列表
    3. listAddNodeHead在表头添加节点
    4. listAddNodeTail在表尾添加节点
    5. listInsertNode在指定节点前后插入节点
    6. listDelNode删除链表中指定节点
  4. 迭代器及相关函数
    1. 迭代器定义
    2. 相关函数
    3. listGetIterator迭代器创建
    4. 迭代器释放迭代指针调整函数
    5. listIterNext迭代函数

1楔子

双向链表为redis列表类型的实现方法之一,列表类型实现除了用到双向链表,还有压缩列表。因为双向链表占用内存较多,所以redis优先采用压缩列表来实现自己的列表类型。压缩列表后续分析,先看看双向链表的代码。

 

双向链表作为一个基本的数据结构,在很多书上都会有描述了,下面就一个个函数看看redis的双向链表的实现方法。文件参见src/adlist.csrc/adlist.h。关于这个命名是双向链表还是双端链表在huangz1990博客上还有争议,哈哈,我就先不管这些概念上的问题了。很多内容参考:http://www.redisbook.com/en/latest/internal-datastruct/adlist.html

 

2双向链表定义

先看下adlist.h中双向链表的节点定义:

[cpp]  view plain copy
  1. /* 
  2.  * 链表节点 
  3.  */  
  4. typedef struct listNode {  
  5.     // 前驱节点  
  6.     struct listNode *prev;  
  7.     // 后继节点  
  8.     struct listNode *next;  
  9.     // 值  
  10.     void *value;  
  11. } listNode;  

 节点listNode定义很常规,一个前驱节点指针,一个后继节点指针,一个值的指针,值的类型为void *,可以存储各种类型。

双向链表本身的定义如下:

[cpp]  view plain copy
  1. /* 
  2.  * 链表 
  3.  */  
  4. typedef struct list {  
  5.     // 头指针  
  6.     listNode *head;  
  7.     // 尾指针  
  8.     listNode *tail;  
  9.     // 节点数量  
  10.     unsigned long len;  
  11.    
  12.     // 复制函数  
  13.     void *(*dup)(void *ptr);  
  14.     // 释放函数  
  15.     void (*free)(void *ptr);  
  16.     // 匹配函数  
  17.     int (*match)(void *ptr, void *key);   
  18. } list;  

即定义了链表头指针,尾指针,长度以及复制,释放和匹配的函数指针。

实际的链表结构图如下:

 

图1 redis双向链表结构示意图(取自huangz1990博客)

正是因为有表头、表尾指针以及链表长度记录,所以redis在表头和表尾插入数据都非常方便,时间复杂度为O(1),这也是lpush,lpop,llen等命令效率较高的原因。


3基本函数

1listCreate:创建列表

[cpp]  view plain copy
  1. /* 
  2.  * 创建一个新链表 
  3.  * 
  4.  * 创建成功时返回链表,创建失败返回 NULL 
  5.  * 
  6.  */  
  7. list *listCreate(void)  
  8. {  
  9.     struct list *list;  
  10.    
  11.     // 为链表结构分配内存  
  12.     if ((list = zmalloc(sizeof(*list))) == NULL)  
  13.         return NULL;  
  14.    
  15.     // 初始化属性  
  16.     list->head = list->tail = NULL;  
  17.     list->len = 0;  
  18.     list->dup = NULL;  
  19.     list->free = NULL;  
  20.     list->match = NULL;  
  21.    
  22.     return list;  
  23. }  

创建链表即是分配struct list的大小的内存,并初始化链表节点数目为0、链表头尾指针和基本函数指针为NULL

2listRelease:释放列表

[cpp]  view plain copy
  1. /* 
  2.  * 释放整个链表(以及列表包含的节点) 
  3.  * 
  4.  * T = O(N),N 为链表的长度 
  5.  */  
  6. void listRelease(list *list)  
  7. {  
  8.     unsigned long len;  
  9.     listNode *current, *next;  
  10.    
  11.     current = list->head;  
  12.     len = list->len;  
  13.     while(len--) {  
  14.         next = current->next;  
  15.         // 如果链表有自带的 free 方法,那么先对节点值调用它  
  16.         if (list->free) list->free(current->value);  
  17.         // 之后再释放节点  
  18.         zfree(current);  
  19.         current = next;  
  20.     }     
  21.     zfree(list); //释放链表本身的管理结构的内存  
  22. }  

释放列表即从表头根据链表节点数目遍历,并依次释放各个节点。如果链表定义了free方法,则先释放节点值占用的内存。最后,释放链表本身的管理结构的内存。

3)listAddNodeHead:在表头添加节点

 

[cpp]  view plain copy
  1. /* 新建一个包含给定 value 的节点,并将它加入到链表的表头 
  2.  * 
  3.  * 出错时,返回 NULL ,不执行动作。 
  4.  * 成功时,返回传入的链表 
  5.  * 
  6.  * T = O(1) 
  7.  */  
  8. list *listAddNodeHead(list *list, void *value)  
  9. {  
  10.     listNode *node;  
  11.    
  12.     if ((node = zmalloc(sizeof(*node))) == NULL)  
  13.         return NULL;  
  14.    
  15.     node->value = value;  
  16.    
  17.     if (list->len == 0) {  
  18.         // 第一个节点  
  19.         list->head = list->tail = node;  
  20.         node->prev = node->next = NULL;  
  21.     } else {  
  22.         // 不是第一个节点  
  23.         node->prev = NULL;  
  24.         node->next = list->head;  
  25.         list->head->prev = node;  
  26.         list->head = node;  
  27.     }  
  28.    
  29.     list->len++;  
  30.    
  31.     return list;  
  32. }  

添加节点的代码很简洁。首先分配内存空间,然后设置节点值为指定的值。然后建立链接关系:如果是第一个节点,直接设置表头和表尾指针为新加入节点;否则,设置新加入节点与原来的头节点的链接关系,并设定头节点为新加入的节点。最后更新链表节点数目,并返回传入的链表。

 

4listAddNodeTail:在表尾添加节点

[cpp]  view plain copy
  1. /* 
  2.  * 新建一个包含给定 value 的节点,并将它加入到链表的表尾 
  3.  * 
  4.  * 出错时,返回 NULL ,不执行动作。 
  5.  * 成功时,返回传入的链表 
  6.  * 
  7.  * T = O(1) 
  8.  */  
  9. list *listAddNodeTail(list *list, void *value)  
  10. {  
  11.     listNode *node;  
  12.    
  13.     if ((node = zmalloc(sizeof(*node))) == NULL)  
  14.         return NULL;  
  15.    
  16.     node->value = value;  
  17.    
  18.     if (list->len == 0) {  
  19.         // 第一个节点  
  20.         list->head = list->tail = node;  
  21.         node->prev = node->next = NULL;  
  22.     } else {  
  23.         // 不是第一个节点  
  24.         node->prev = list->tail;  
  25.         node->next = NULL;  
  26.         list->tail->next = node;  
  27.         list->tail = node;  
  28.     }  
  29.    
  30.     list->len++;  
  31.    
  32.     return list;  
  33. }  

与表头添加节点类似,只是添加在了链表尾部而已。

 

5)listInsertNode:在指定节点前后插入节点

[cpp]  view plain copy
  1. /* 
  2.  * 创建一个包含值 value 的节点 
  3.  * 并根据 after 参数的指示,将新节点插入到 old_node 的之前或者之后 
  4.  * 
  5.  * T = O(1) 
  6.  */  
  7. list *listInsertNode(list *list, listNode *old_node, void *value, int after) {  
  8.     listNode *node;  
  9.    
  10.     if ((node = zmalloc(sizeof(*node))) == NULL)  
  11.         return NULL;  
  12.    
  13.     node->value = value;  
  14.    
  15.     if (after) {  
  16.         // 插入到 old_node 之后  
  17.         node->prev = old_node;  
  18.         node->next = old_node->next;  
  19.         // 处理表尾节点  
  20.         if (list->tail == old_node) {  
  21.             list->tail = node;  
  22.         }     
  23.     } else {  
  24.         // 插入到 old_node 之前  
  25.         node->next = old_node;  
  26.         node->prev = old_node->prev;  
  27.         // 处理表头节点  
  28.         if (list->head == old_node) {  
  29.             list->head = node;  
  30.         }     
  31. }     
  32.     // 更新前置节点和后继节点的指针  
  33.     if (node->prev != NULL) {  
  34.         node->prev->next = node;  
  35.     }  
  36.     if (node->next != NULL) {  
  37.         node->next->prev = node;  
  38.     }  
  39.    
  40.     // 更新链表节点数量  
  41.     list->len++;  
  42.    
  43.     return list;  
  44. }  

这里需要注意的是处理表头和表尾节点,以及后面更新前置节点和后继节点指针部分。

6)listDelNode:删除链表中指定节点

[cpp]  view plain copy
  1. /* 
  2.  * 删除链表中指定的节点 
  3.  * 清除节点私有值(private value)的工作由调用者完成 
  4.  * 
  5.  * T = O(1) 
  6.  */  
  7. void listDelNode(list *list, listNode *node)  
  8. {  
  9.     // 处理前驱节点的指针  
  10.     if (node->prev)  
  11.         node->prev->next = node->next;  
  12.     else  
  13.         list->head = node->next;  
  14.    
  15.     // 处理后继节点的指针  
  16.     if (node->next)  
  17.         node->next->prev = node->prev;  
  18.     else  
  19.         list->tail = node->prev;  
  20.    
  21.     // 释放节点值  
  22.     if (list->free) list->free(node->value);  
  23.    
  24.     // 释放节点  
  25.     zfree(node);  
  26.    
  27.     // 更新链表节点数量  
  28.     list->len--;  
  29. }  


迭代器及相关函数

迭代器定义

Redis为双向链表还实现了一个迭代器,可以从两个方向迭代链表。迭代器定义如下:

[cpp]  view plain copy
  1. /* 
  2.  * 链表迭代器 
  3.  */  
  4. typedef struct listIter {  
  5.     // 下一节点  
  6.     listNode *next;  
  7.     // 迭代方向  
  8.     int direction;  
  9. } listIter;  

direction如果设定为AL_START_HEAD(0),则从表头到表尾迭代。

direction如果设定为AL_START_TAIL(1),则从表尾向表头迭代。

相关函数

1listGetIterator:迭代器创建

[cpp]  view plain copy
  1. /* 
  2. * 这个函数不处理失败情形 
  3. * 
  4. * T = O(1) 
  5. */  
  6. listIter *listGetIterator(list *list, int direction)  
  7. {  
  8.     listIter *iter;  
  9.    
  10.     if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL;  
  11.    
  12.     // 根据迭代的方向,将迭代器的指针指向表头或者表尾  
  13.     if (direction == AL_START_HEAD)  
  14.         iter->next = list->head;  
  15.     else  
  16.         iter->next = list->tail;  
  17.    
  18.     // 记录方向  
  19.     iter->direction = direction;  
  20.    
  21.     return iter;  
  22. }  

2)迭代器释放,迭代指针调整函数

[cpp]  view plain copy
  1. /* 
  2.  * 释放迭代器 iter 
  3.  * 
  4.  * T = O(1) 
  5.  */  
  6. void listReleaseIterator(listIter *iter) {  
  7.     zfree(iter);  
  8. }  
  9.    
  10. /* 
  11.  * 将迭代器 iter 的迭代指针倒回 list 的表头 
  12.  * 
  13.  * T = O(1) 
  14.  */  
  15. void listRewind(list *list, listIter *li) {  
  16.     li->next = list->head;  
  17.     li->direction = AL_START_HEAD;  
  18. }  
  19.    
  20. /* 
  21.  * 将迭代器 iter 的迭代指针倒回 list 的表尾 
  22.  * 
  23.  * T = O(1) 
  24.  */  
  25. void listRewindTail(list *list, listIter *li) {  
  26.     li->next = list->tail;  
  27.     li->direction = AL_START_TAIL;  
  28. }  

3)listIterNext:迭代函数

[cpp]  view plain copy
  1. /* 
  2.  * 返回迭代器的当前节点 
  3.  * 
  4.  * 可以使用 listDelNode() 删除当前节点,但是不可以删除其他节点。 
  5.  * 
  6.  * 函数要么返回当前节点,要么返回 NULL ,因此,常见的用法是: 
  7.  *  
  8.  * iter = listGetIterator(list,<direction>); 
  9.  * while ((node = listNext(iter)) != NULL) { 
  10.  *     doSomethingWith(listNodeValue(node)); 
  11.  * } 
  12.  * 
  13.  * T = O(1) 
  14.  */  
  15. listNode *listNext(listIter *iter)  
  16. {  
  17.     listNode *current = iter->next;  
  18.    
  19.     if (current != NULL) {  
  20.         // 根据迭代方向,选择节点  
  21.         if (iter->direction == AL_START_HEAD)  
  22.             iter->next = current->next;  
  23.         else  
  24.             iter->next = current->prev;  
  25.     }  
  26.    
  27.     return current;  
  28. }  

迭代器在迭代过程中,根据迭代方向,每次更新next指针即可。

你可能感兴趣的:(Redis源码学习3-基本数据结构之双向链表)