redis 5.0.7 源码阅读——双向链表

redis中双向链表相关的文件为:adlist.h与adlist.c

一、数据结构

redis里定义的双向链表,与普通双向链表大致相同

单个节点:

1 typedef struct listNode {2     struct listNode *prev;3     struct listNode *next;4     void *value;5 } listNode;

链表:

1 typedef struct list {2     listNode *head;3     listNode *tail;4     void *(*dup)(void *ptr);5     void (*free)(void *ptr);6     int (*match)(void *ptr, void *key);7     unsigned long len;8 } list;

链表以函数指针的方式,实现了复制、销毁与比较的方法的多态。

迭代器:

1 typedef struct listIter {2     listNode *next;3     int direction;4 } listIter;

迭代器中有个成员变量direction,用于表示当前遍历的方向。

大致结构:

1 /* 2 +-------------------+        +----------------> +--------------+ <-------+ 3 |listNode *head     |--------+                  |listNode *prev|-->NULL  | 4 +-------------------+                           +--------------+         | 5 |listNode *tail     |--------+                  |listNode *next|----+    | 6 +-------------------+        |                  +--------------+    |    | 7 |void *(*dup)(...)  |        |                  |void *value   |    |    | 8 +-------------------+        |                  +--------------+    |    | 9 |void (*free)(...)  |        |                                      |    |10 +-------------------+        |                                      |    |11 |int (*match)(...)  |        |                                      |    |12 +-------------------+        +----------------> +--------------+ <--+    |13 |unsigned long len  |                           |listNode *prev|---------+14 +-------------------+                           +--------------+15                                                 |listNode *next|-->NULL16                                                 +--------------+17                                                 |void *value   |18                                                 +--------------+    19 */

二、创建

redis中创建一个初始双向链表比较简单,只要分配好内存,并给成员变量赋初值就可以了

1 list *listCreate(void) 2 { 3     struct list *list; 4  5     if ((list = zmalloc(sizeof(*list))) == NULL) 6         return NULL; 7     list->head = list->tail = NULL; 8     list->len = 0; 9     list->dup = NULL;10     list->free = NULL;11     list->match = NULL;12     return list;13 }

redis中提供了头插法、尾插法以及指定位置插入节点三种方式向链表中添加节点,与普通双向链表无异,此处不做详细叙述。

三、销毁

因链表中每个节点的value可能指向堆空间,故不能直接把list结构体free,这样会造成内存泄露。需要先将每个节点的value释放,才可以free结构体

清空所有节点:

1 void listEmpty(list *list) 2 { 3     unsigned long len; 4     listNode *current, *next; 5  6     current = list->head; 7     len = list->len; 8     while(len--) { 9         next = current->next;10         //若指定了销毁的函数,则使用指定的函数进行销毁value11         if (list->free) list->free(current->value);12         zfree(current);13         current = next;14     }15     list->head = list->tail = NULL;16     list->len = 0;17 }

销毁链表:

1 void listRelease(list *list)2 {3     listEmpty(list);4     zfree(list);5 }

同样,redis的链表提供了与普通链表相同的删除单个节点的操作,此处也不做叙述。

四、迭代器操作

redis中提供了获取迭代器的接口

1 listIter *listGetIterator(list *list, int direction) 2 { 3     listIter *iter; 4  5     if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL; 6     if (direction == AL_START_HEAD) 7         iter->next = list->head; 8     else 9         iter->next = list->tail;10     iter->direction = direction;11     return iter;12 }

以AL_START_HEAD为例,生成好的迭代器结构如下:

1 /* 2 +-------------------+    +---> +--------------+ <-------+----+ 3 |listNode *head     |----+     |listNode *prev|-->NULL  |    |   4 +-------------------+          +--------------+         |    |  +--------------+ 5 |listNode *tail     |----+     |listNode *next|----+    |    +--|listNode *next| 6 +-------------------+    |     +--------------+    |    |       +--------------+ 7 |void *(*dup)(...)  |    |     |void *value   |    |    |       |int direction | 8 +-------------------+    |     +--------------+    |    |       +--------------+ 9 |void (*free)(...)  |    |                         |    |10 +-------------------+    |                         |    |11 |int (*match)(...)  |    |                         |    |12 +-------------------+    +---> +--------------+ <--+    |13 |unsigned long len  |          |listNode *prev|---------+14 +-------------------+          +--------------+15                                |listNode *next|-->NULL16                                +--------------+17                                |void *value   |18                                +--------------+    19 */

迭代器的next方法:

1 listNode *listNext(listIter *iter) 2 { 3     listNode *current = iter->next; 4  5     if (current != NULL) { 6         if (iter->direction == AL_START_HEAD) 7             iter->next = current->next; 8         else 9             iter->next = current->prev;10     }11     return current;12 }

调用一次之后的结构:

1 /* 2 +-------------------+    +---> +--------------+ <-------+ 3 |listNode *head     |----+     |listNode *prev|-->NULL  |       4 +-------------------+          +--------------+         |       +--------------+ 5 |listNode *tail     |----+     |listNode *next|----+    |    +--|listNode *next| 6 +-------------------+    |     +--------------+    |    |    |  +--------------+ 7 |void *(*dup)(...)  |    |     |void *value   |    |    |    |  |int direction | 8 +-------------------+    |     +--------------+    |    |    |  +--------------+ 9 |void (*free)(...)  |    |                         |    |    |10 +-------------------+    |                         |    |    |11 |int (*match)(...)  |    |                         |    |    |12 +-------------------+    +---> +--------------+ <--+----|----+    13 |unsigned long len  |          |listNode *prev|---------+14 +-------------------+          +--------------+15                                |listNode *next|-->NULL16                                +--------------+17                                |void *value   |18                                +--------------+    19 */

再次调用:

1 /* 2 +-------------------+    +---> +--------------+ <-------+ 3 |listNode *head     |----+     |listNode *prev|-->NULL  |       4 +-------------------+          +--------------+         |       +--------------+ 5 |listNode *tail     |----+     |listNode *next|----+    |    +--|listNode *next| 6 +-------------------+    |     +--------------+    |    |    |  +--------------+ 7 |void *(*dup)(...)  |    |     |void *value   |    |    |    |  |int direction | 8 +-------------------+    |     +--------------+    |    |    |  +--------------+ 9 |void (*free)(...)  |    |                         |    |    |10 +-------------------+    |                         |    |    |11 |int (*match)(...)  |    |                         |    |    |12 +-------------------+    +---> +--------------+ <--+    |    +-->NULL    13 |unsigned long len  |          |listNode *prev|---------+14 +-------------------+          +--------------+15                                |listNode *next|-->NULL16                                +--------------+17                                |void *value   |18                                +--------------+    19 */

调用next函数的返回值为调用之前的listNode首地址

五、其它操作

redis的双向链表还提供了其它操作。其中,查找指定的key与复制整个list依赖于迭代器的使用,并使用到自定义的比较/复制方法。

除此之外,还提供了类似随机读取的方式,其内部实现为遍历,且“越界”时返回NULL。同时,它支持index为负数,表示从尾开始。类似旋转的操作,把尾节点移至原头节点之前,成为新的头节点。当然,还有拼接两个链表的操作。

redis 5.0.7 下载链接

http://download.redis.io/releases/redis-5.0.7.tar.gz

源码阅读顺序参考:

https://github.com/huangz1990/blog/blob/master/diary/2014/how-to-read-redis-source-code.rst

(0)

相关推荐

  • LeetCode之Merge Two Sorted Lists

    LeetCode之Merge Two Sorted Lists

  • ​LeetCode刷题实战369:给单链表加一

    算法的重要性,我就不多说了吧,想去大厂,就必须要经过基础知识和业务逻辑面试+算法面试.所以,为了提高大家的算法能力,这个公众号后续每天带大家做一道算法题,题目就从LeetCode上面选 ! 今天和大家 ...

  • ​LeetCode刷题实战143: 重排链表

    算法的重要性,我就不多说了吧,想去大厂,就必须要经过基础知识和业务逻辑面试+算法面试.所以,为了提高大家的算法能力,这个公众号后续每天带大家做一道算法题,题目就从LeetCode上面选 ! 今天和大家 ...

  • redis 5.0.7 源码阅读——动态字符串sds

    redis中动态字符串sds相关的文件为:sds.h与sds.c 一.数据结构 redis中定义了自己的数据类型"sds",用于描述 char*,与一些数据结构 1 typedef ...

  • TelloPy-develop-0.7.0源码阅读.1

    最近我在反思,为什么我看了那么多书,为什么还是写不出大型的程序?我也很苦恼,我想了下.应该还是看的源码少的过,古人曾经说过熟读唐诗三百首,不会吟诗也会吟 .在读源码的选择上,我没有选择太复杂的开源库, ...

  • 一个超级实用的源码阅读小技巧

    在学习编程的路途漫漫,优秀的源码是非常珍贵的学习资源,阅读源码也是有效提高自己的一个好方法. 工欲善其事必先利其器: 我发现函数调用图可以让我们更加直观地了解到源码函数直接的调用和层次关系,提高阅读源 ...

  • MySQL 8.0.22 源码编译安装全过程

    墨墨导读: Mysql的8.0版本出来已经有一段时间了,近期研究下源码调试.整个编译过程越来越复杂了. 近期研究下源码调试,MySQL5.7版本源码安装还是比较简单的,有很多例子参考.所以这次选择My ...

  • spark源码阅读--shuffle过程分析 ShuffleManager(一)

    ShuffleManager(一) 本篇,我们来看一下spark内核中另一个重要的模块,Shuffle管理器ShuffleManager.shuffle可以说是分布式计算中最重要的一个概念了,数据的j ...

  • dubbo源码阅读之服务目录

    服务目录 服务目录对应的接口是Directory,这个接口里主要的方法是 List<Invoker<T>> list(Invocation invocation) throws ...

  • 手把手教学APK反编译实现源码阅读

    手把手教学APK反编译实现源码阅读

  • Vue2 源码阅读(三) 双向绑定原理

    Vue2 源码阅读(三) 双向绑定原理

  • Vue2.x 响应式部分源码阅读记录

    之前也用了一段时间Vue,对其用法也较为熟练了,但是对各种用法和各种api使用都是只知其然而不知其所以然.最近利用空闲时间尝试的去看看Vue的源码,以便更了解其具体原理实现,跟着学习学习. Proxy ...