当前位置: 首页 > 编程日记 > 正文

Redis源码解析——双向链表

相对于之前介绍的字典和SDS字符串库,Redis的双向链表库则是非常标准的、教科书般简单的库。但是作为Redis源码的一部分,我决定还是要讲一讲的。(转载请指明出于breaksoftware的csdn博客)

基本结构

首先我们看链表元素的结构。因为是双向链表,所以其基本元素应该有一个指向前一个节点的指针和一个指向后一个节点的指针,还有一个记录节点值的空间

typedef struct listNode {struct listNode *prev;struct listNode *next;void *value;
} listNode;

因为双向链表不仅可以从头开始遍历,还可以从尾开始遍历,所以链表结构应该至少有头和尾两个节点的指针。

但是作为一个可以承载各种类型数据的链表,还需要链表使用者提供一些处理节点中数据的能力。因为这些数据可能是用户自定义的,所以像复制、删除、对比等操作都需要用户来告诉框架。在《Redis源码解析——字典结构》一文中,我们看到用户创建字典时需要传入的dictType结构体,就是一个承载数据的上述处理方法的载体。但是Redis在设计双向链表时则没有使用一个结构来承载这些方法,而是在链表结构中定义了

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,我觉得从必要性上来说是没有必要的。有len字段的一个优点是不用每次计算链表长度时都要做一次遍历操作,缺点便是导出需要维护这个变量。

创建和释放链表

链表创建的过程比较简单。只是申请了一个list结构体空间,然后各个字段设置为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;
}

但是比较有意思的是,创建链表时没有设定链表类型——没有设置复制、释放、对比等方法的指针。作者单独提供了一些宏来设置,个人觉得这种割裂开的设计不是很好

#define listSetDupMethod(l,m) ((l)->dup = (m))
#define listSetFreeMethod(l,m) ((l)->free = (m))
#define listSetMatchMethod(l,m) ((l)->match = (m))

释放链表的操作就是从头部向尾部一个个释放节点,迭代的方法是通过判断不断减小的链表长度字段len是否为0来进行。之前说过,len其实没有必要性,只要判断节点的next指针为空就知道到结尾了。

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);
}

新增节点

新增节点分为三种:头部新增、尾部新增和中间新增。头部和尾部新增都很简单,只是需要考虑一下新增之前链表是不是空的。如果是空的,要设置新增节点的指向前后指针为NULL,还要让链表的头尾指针都指向新增的节点

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;
}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;
}

上述代码还说明一个问题,新建节点的数据指针指向传入的value内容,而没有使用复制操作将value所指向的数据复制下来。于是插入链表中的数据,要保证在链表生存周期之内都要有效。

在链表中间插入节点时,可以指定插入到哪个节点前还是后。这个场景下则需要考虑作为坐标的节点是否为链表的头结点或者尾节点;如果是,可能还要视新插入的节点的位置更新链表的头尾节点指向。

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;
}

删除节点

删除节点时要考虑节点是否为链表的头结点或者尾节点。如果是则要更新链表的信息,否则只要更新待删除的节点前后节点指向关系。

void listDelNode(list *list, listNode *node)
{if (node->prev)node->prev->next = node->next;elselist->head = node->next;if (node->next)node->next->prev = node->prev;elselist->tail = node->prev;if (list->free) list->free(node->value);zfree(node);list->len--;
}

创建和释放迭代器

迭代器是一种辅助遍历链表的结构,它分为向前迭代器和向后迭代器。我们在迭代器结构中可以发现方向类型变量

typedef struct listIter {listNode *next;int direction;
} listIter;

创建一个迭代器,需要指定方向,从而可以让迭代器的next指针指向链表的头结点或者尾节点

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;elseiter->next = list->tail;iter->direction = direction;return 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;
}

因为通过listGetIterator创建的迭代器是在堆上动态分配的,所以不使用时要释放

void listReleaseIterator(listIter *iter) {zfree(iter);
}

迭代器遍历

迭代器的遍历其实就是简单的通过节点向前向后指针访问到下一个节点的过程

listNode *listNext(listIter *iter)
{listNode *current = iter->next;if (current != NULL) {if (iter->direction == AL_START_HEAD)iter->next = current->next;elseiter->next = current->prev;}return current;
}

链表复制

链表的复制过程就是通过一个从头向尾访问的迭代器,将原链表中的数据复制到新建的链表中。但是这儿有个地方需要注意下,就是复制操作可能分为深拷贝和浅拷贝。如果我们通过listSetDupMethod设置了数据的复制方法,则使用该方法进行数据的复制,然后将复制出来的新数据放到新的链表中。如果没有设置,则只是把老链表中元素的value字段赋值过去。

list *listDup(list *orig)
{list *copy;listIter iter;listNode *node;if ((copy = listCreate()) == NULL)return NULL;copy->dup = orig->dup;copy->free = orig->free;copy->match = orig->match;listRewind(orig, &iter);while((node = listNext(&iter)) != NULL) {void *value;if (copy->dup) {value = copy->dup(node->value);if (value == NULL) {listRelease(copy);return NULL;}} elsevalue = node->value;if (listAddNodeTail(copy, value) == NULL) {listRelease(copy);return NULL;}}return copy;
}

查找元素

查找元素同样是通过迭代器遍历整个链表,然后视用户是否通过listSetMatchMethod设置对比方法来决定是使用用户定义的方法去对比,还是直接使用value去对比。如果是使用value直接去对比,则是强对比,即要求对比的数据和链表的数据在内存中位置是一样的。

listNode *listSearchKey(list *list, void *key)
{listIter iter;listNode *node;listRewind(list, &iter);while((node = listNext(&iter)) != NULL) {if (list->match) {if (list->match(node->value, key)) {return node;}} else {if (key == node->value) {return node;}}}return NULL;
}

通过下标访问链表

下标可以是负数,代表返回从后数第几个元素。

listNode *listIndex(list *list, long index) {listNode *n;if (index < 0) {index = (-index)-1;n = list->tail;while(index-- && n) n = n->prev;} else {n = list->head;while(index-- && n) n = n->next;}return n;
}

结尾节点前移为头结点

这个方法在Redis代码中使用比较多。它将链表最后一个节点移动到链表头部。我想设计这么一个方法是为了让链表内容可以在无状态记录的情况下被均匀的轮询到。

void listRotate(list *list) {listNode *tail = list->tail;if (listLength(list) <= 1) return;/* Detach current tail */list->tail = tail->prev;list->tail->next = NULL;/* Move it as head */list->head->prev = tail;tail->prev = NULL;tail->next = list->head;list->head = tail;
}

相关文章:

12月第三周安全要闻回顾:浏览器安全不容忽视,SSL弱点影响网站安全

本周&#xff08;081215至081221&#xff09;安全方面的新闻众多&#xff0c;主要集中在***与威胁趋势方面。浏览器安全方向波澜起伏&#xff0c;微软推出了针对上周公开的IE7新漏洞的紧急安全补丁&#xff0c;但目前互联网上针对该漏洞的大规模***仍在继续&#xff0c;******的…

GPT2文本生成有问题?这里有些潜在解决思路

作者 | Leo Gao译者 | 凯隐编辑 | 夕颜出品 | AI科技大本营&#xff08;ID: rgznai100&#xff09; 【导读】在过去的一年中&#xff0c;人们对文本生成模型的兴趣重新燃起&#xff0c;这在很大程度上要归功于GPT2&#xff0c;它主要展示了使用更大模型、更大数据和更大计算量的…

HTML5学习之二:HTML5中的表单2

&#xff08;本内容部分节选自《HTML 5从入门到精通》) 对表单的验证 ———————————————————————————————————————————————————————— •1、required属性 required属性主要目的是确保表单控件中的值已填写。在提交时&…

Redis源码解析——有序整数集

有序整数集是Redis源码中一个以大尾&#xff08;big endian&#xff09;形式存储&#xff0c;由小到大排列且无重复的整型集合。它存储的类型包括16位、32位和64位的整型数。在介绍这个库的实现前&#xff0c;我们还需要先熟悉下大小尾内存存储机制。&#xff08;转载请指明出于…

GitHub标星1.2w+,Chrome最天秀的插件都在这里

作者 | Rocky0429来源 | Python空间&#xff08;ID: Devtogether&#xff09;大家好&#xff0c;我是 Rocky0429&#xff0c;一个沉迷 Chrome 不能自拔的蒟蒻...作为一个在远古时代用过什么 IE、360、猎豹等浏览器的资深器哥&#xff0c;当我第一次了解 Chrome 的时候&#xff…

基础篇 第四节 项目进度计划编辑 之 任务关联性设定

1.任务关联性的类型 ◎完成 —— 开始 FS ◎开始 —— 开始 SS ◎开始 —— 完成 SF 完成 —— 完成 FF 2.设定任务关联性 三种方法&#xff1a; ◎在条形图中直接拖拽 ◎在“前置任务”列中编辑 ◎在“任务信息”中的“前置任务”选项卡中编辑 3.设定“延隔时间” 延隔时间小于…

开坑,写点Polymer 1.0 教程第3篇——组件注册与创建

之前一篇算是带大家大致领略了一下Polymer的风采。这篇我们稍微深入一丢丢&#xff0c;讲下组件的注册和创建。 创建自定义组件的几种方式 这里我们使用Polymer函数注册了一个自定义组件"my-element" // register an element Polymer({is: my-element,created: funct…

Redis源码解析——Zipmap

本文介绍的是Redis中Zipmap的原理和实现。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 基础结构 Zipmap是为了实现保存Pair(String,String)数据的结构&#xff0c;该结构包含一个头信息、一系列字符串对&#xff08;之后把一个“字符串对”称为一个“元素…

IIS7入门之旅:(3)CGI application和FastCGI application的区别

前言&#xff1a; 一如既往地&#xff0c;IIS支持通过提供pluggable module来提供对第3方script的支持,例如php等。在IIS7中&#xff0c;对于CGI的支持有了一个新的变化&#xff0c;就是同时提供了2种CGI支持模块&#xff0c;分别为&#xff1a;CGIModule (cgi.dll)和FastCGIMo…

抗击疫情!阿里云为加速新药疫苗研发提供免费AI算力

1月29日&#xff0c;阿里云正式宣布&#xff1a;疫情期间&#xff0c;向全球公共科研机构免费开放一切AI算力&#xff0c;以加速本次新型肺炎新药和疫苗研发。 目前&#xff0c;中国疾控中心已成功分离病毒&#xff0c;疫苗研发和药物筛选仍在争分夺秒地进行。新药和疫苗研发期…

SpriteBuilder中如何平均拉伸精灵帧动画的距离

首先要在Timeline中选中所有的精灵帧&#xff0c;可以通过如下2种的任意一种办法达成&#xff1a; 1按下Shift键的同时鼠标单击它们 2鼠标在Timeline空白区拖拽直到拉出的矩形包围住所有精灵帧方块后放开鼠标。 你可以通过查看Timeline中精灵帧方块上是否有阴影来辨识是否选中…

C++拾趣——类构造函数的隐式转换

之前看过一些批判C的文章&#xff0c;大致意思是它包含了太多的“奇技淫巧”&#xff0c;并不是一门好的语言。我对这个“奇技淫巧”的描述颇感兴趣&#xff0c;因为按照批判者的说法&#xff0c;C的一些特性恰巧可以让一些炫耀技术的同学有了炫耀的资本——毕竟路人皆知的东西…

数十名工程师作战5天,阿里达摩院连夜研发智能疫情机器人

作者 | Just出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;新型肺炎疫情防控战在各大互联网科技公司拉响&#xff0c;阿里、百度等公司陆续对外提供相应技术和产品。当前&#xff0c;疫情当前防控一线人员紧缺&#xff0c;多地政务热线迎来大波问询市民&#xff0c;…

路由器互联端口处于不同网段的路由方法和原理

如下图&#xff1a;两台cisco路由器相连接的两个端口不在同一个网络&#xff0c;如何实现两台路由器的互联&#xff1f;初看似乎绝对不可能&#xff0c;但是这是可行的&#xff0c;而且我已经把这个变成了现实。这里讲述配置的方法&#xff0c;以及解释原理。这个就要讲到路由原…

上网行为管理产品选型简单考量

信息化浪潮汹涌向前&#xff0c;人们的生活、工作、学习越来越离不开IT&#xff0c;离不开网络。 对于很多组织来讲&#xff0c;网络就意味着效率、甚至生产力&#xff0c;协同办公、决策、科研、资金划拨等等都对网络有了前所未有的依赖。网络应用日益复杂、多变、动态特征发展…

码农技术炒股之路——配置管理器、日志管理器

配置管理器和日志管理器是项目中最为独立的模块。我们可以很方便将其剥离出来供其他Python工程使用。文件的重点将是介绍Python单例和logging模块的使用。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 配置管理器 在《码农技术炒股之路——架构和设计》中我…

“数学不好,干啥都不行!”资深程序员:别再瞎努力了!

之前很多程序员读者向我们反馈&#xff1a;1&#xff09;做算法优化时&#xff0c;只能现搬书里的算法&#xff0c;遇到不一样的问题&#xff0c;就不会了。2&#xff09;面试一旦涉及到算法和数据结构&#xff0c;如果数学不行&#xff0c;面试基本就凉凉了。3&#xff09;算法…

受限列表 队列与栈

2019独角兽企业重金招聘Python工程师标准>>> 队列与栈为受限列表&#xff0c;队列为先入先出型列表&#xff0c;而栈为先入后出型列表&#xff0c;有关列表的实现可以查看 http://my.oschina.net/u/2011113/blog/514713 。 结构图为 Queue实现了IQueue接口&#xff…

码农技术炒股之路——数据库管理器、正则表达式管理器

我选用的数据库是Mysql。选用它是因为其可以满足我的需求&#xff0c;而且资料多。因为作为第三方工具&#xff0c;难免有一些配置问题。所以本文也会讲解一些和Mysql配置及开发相关的问题。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 数据库管理器 Mysq…

Overview of ISA and TMG Networking and ISA Networking Case Study (Part 1)

老方说&#xff1a;此篇文章摘自ISASERVER.ORG网站&#xff0c;出自Thomas Shinder达人之手。严重建议ISA爱好者看看。Published: Dec 16, 2008 Updated: Jan 21, 2009 Author: Thomas Shinder What ISA/TMG firewall Networks are about and how the firewall uses these ne…

阿里云免费开放一切AI算力,加速新型冠状病毒新药和疫苗研发

近日&#xff0c;阿里云宣布&#xff0c;为了帮助加速新药和疫苗研发&#xff0c;将向全球公共科研机构免费开放一切AI算力。目前&#xff0c;中国疾控中心已成功分离病毒&#xff0c;疫苗研发和药物筛选仍在争分夺秒地进行。新药和疫苗研发期间&#xff0c;需要进行大量的数据…

ASP.net(C#)批量上传图片(完整版)

来自&#xff1a;http://blog.itpub.net/9869521/viewspace-667955/ 这篇关于ASP.Net批量上传图片的文章写得非常好&#xff0c;偶尔在网上看到想转载到这里&#xff0c;却费劲了周折。为了更新这篇文章&#xff0c;我用了近半个小时&#xff0c;网上的转载都残缺不全&#xff…

码农技术炒股之路——任务管理器

系统任务和普通任务都是通过任务管理器调度的。它们的区别是&#xff1a;系统任务在程序运行后即不会被修改&#xff0c;而普通任务则会被修改。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 为什么要有这样的设计&#xff1f;因为我希望它是一个可以不用停…

面对新型肺炎疫情,AI能做什么?

作者 | 马超出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;根据最新的新型冠状病毒疫情通报&#xff0c;截至1月30日24时&#xff0c;国家卫生健康委公布确诊病例9692例&#xff0c;重症病例1527例&#xff0c;累计死亡病例213例&#xff0c;另有疑似病例15238例。为…

大家帮忙.谢谢!..(急急急急急)

大家帮忙.谢谢!..(急急急急急) Delphi / Windows SDK/APIhttp://www.delphi2007.net/DelphiDB/html/delphi_20061218224617231.htmlprocedure TForm1.Button4Click(Sender: TObject); var P : pstring; i, j : integer; begin GetMem(p, sizeof(stri…

HDU4866 Shooting (要持久段树)

意甲冠军&#xff1a; 给你一些并行x行轴。总是询问坐标x的顶部之前&#xff0c;k一个段高度&#xff0c;。标题是必须在线。思路&#xff1a; 首先要会可持久化线段树(又称主席树和函数式线段树)。不会的能够去做下POJ 2104。 把全部线段高度离散化&#xff0c;作为结点建线段…

C++过去的这一年

作者 | Bartek译者 | 苏本如&#xff0c;责编 | 屠敏出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;【导读】本文旨在让我们回顾 C 2019年里的变化和发展&#xff01;我们将重点关注本年度里 C 上发生的重大事件&#xff0c;标准的发展&#xff0c;工具的变化等等…

码农技术炒股之路——抓取股票基本信息、实时交易信息、主力动向信息

从本节开始&#xff0c;我们开始介绍各个抓取和备份业务。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 因为我们数据库很多&#xff0c;数据库中表也很多&#xff0c;所以我们需要一个自动检测并创建数据库和表的功能。在《码农技术炒股之路——数据库管理…

TemplateBuilder

http://msdn.microsoft.com/zh-cn/vstudio/system.web.ui.templatebuilder_members(VS.85).aspx TemplateBuilder 成员TemplateBuilder 成员支持在生成模板及其包含的子控件时使用的页分析器。 下表列出了由 TemplateBuilder 类型公开的成员。 公共构造函数 名称 说明 Templat…