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

redis中动态字符串sds相关的文件为: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|-->NULL
16                                                 +--------------+
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         //若指定了销毁的函数,则使用指定的函数进行销毁value
11         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|-->NULL
16                                +--------------+
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|-->NULL
16                                +--------------+
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|-->NULL
16                                +--------------+
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

原文地址:https://www.cnblogs.com/chinxi/p/12233306.html

时间: 2024-10-09 20:01:39

redis 5.0.7 源码阅读——双向链表的相关文章

redis 5.0.7 源码阅读——跳跃表skiplist

redis中并没有专门给跳跃表两个文件.在5.0.7的版本中,结构体的声明与定义.接口的声明在server.h中,接口的定义在t_zset.c中,所有开头为zsl的函数. 一.数据结构 单个节点: typedef struct zskiplistNode { //key,唯一 sds ele; //分值,可重复 double score; //后退指针 struct zskiplistNode *backward; //层 struct zskiplistLevel { //前进指针 struc

redis 4.0.8 源码包安装集群

系统:centos 6.9软件版本:redis-4.0.8,rubygems-2.7.7,gcc version 4.4.7 20120313,openssl-1.1.0h,zlib-1.2.11 yum install c++ gcc 1.安装redis tar -xf redis-4.0.8.tar.gz cd redis-4.0.8 make make install PREFIX=/redis-cluster 2.安装ruby和rubygems及相关组件(zlib和openssl) 安装

Redis 2.8.9 源码阅读笔记,索引目录

字符串对象的实现 双端链表 字典的实现 跳表的实现 IO操作实现 RDB模块 AOF模块 ae事件模块

Redis源码阅读-Adlist双向链表

Redis源码阅读-链表部分- 链表数据结构在Adlist.h   Adlist.c Redis的链表是双向链表,内部定义了一个迭代器. 双向链表的函数主要是链表创建.删除.节点插入.头插入.尾插入.第N个节点.节点迭代遍历.链表复制.链表rotate.节点删除 typedef struct listNode { struct listNode *prev; struct listNode *next; void *value; //定义为void *类型,方便用户自行使用自己的数据结构 } l

Redis源码阅读(一)事件机制

Redis源码阅读(一)事件机制 Redis作为一款NoSQL非关系内存数据库,具有很高的读写性能,且原生支持的数据类型丰富,被广泛的作为缓存.分布式数据库.消息队列等应用.此外Redis还有许多高可用特性,包括数据持久化,主从模式备份等等,可以满足对数据完整有一定要求的场景. 而且Redis的源码结构简单清晰,有大量材料可以参阅:通过阅读Redis源码,掌握一些常用技术在Redis中的实现,相信会对个人编程水平有很大帮助.这里记录下我阅读Redis源码的心得.从我自己比较关心的几个技术点出发,

Redis源码阅读(二)高可用设计——复制

Redis源码阅读(二)高可用设计-复制 复制的概念:Redis的复制简单理解就是一个Redis服务器从另一台Redis服务器复制所有的Redis数据库数据,能保持两台Redis服务器的数据库数据一致. 使用场景:复制机制很实用,在客户端并发访问量很大,单台Redis扛不住的情况下,可以部署多台Redis复制相同的数据,共同对外提供服务,提高Redis并发访问处理能力.当然这种通过复制方式部署多台Redis以提高并发处理能力的方式只适用于客户端大部分访问为读数据请求的场景.此外,Redis从2.

Redis源码阅读一:简单动态字符串SDS

源码阅读基于Redis4.0.9 SDS介绍 redis 127.0.0.1:6379> SET dbname redis OK redis 127.0.0.1:6379> GET dbname "redis" 从上面的例子可以看到,key为dbname的值是一个字符串"redis" Redis源码是用c写成,但并没有使用c的字符串.c的字符串有以下缺点: 没有储存字符串长度的变量,获取长度只能靠遍历字符串 扩容麻烦.没有相应保护,容易造成缓冲区溢出 更

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

redis中动态字符串sds相关的文件为:sds.h与sds.c 一.数据结构 redis中定义了自己的数据类型"sds",用于描述 char*,与一些数据结构 1 typedef char *sds; 2 3 /* Note: sdshdr5 is never used, we just access the flags byte directly. 4 * However is here to document the layout of type 5 SDS strings. *

koa源码阅读[0]

koa源码阅读[0] Node.js也是写了两三年的时间了,刚开始学习Node的时候,hello world就是创建一个HttpServer,后来在工作中也是经历过Express.Koa1.x.Koa2.x以及最近还在研究的结合着TypeScript的routing-controllers(驱动依然是Express与Koa).用的比较多的还是Koa版本,也是对它的洋葱模型比较感兴趣,所以最近抽出时间来阅读其源码,正好近期可能会对一个Express项目进行重构,将其重构为koa2.x版本的,所以,