1.介绍

List是通过ListNode实现的双向链表。

1.双端:获取某个结点的前驱和后继结点都是O(1)

2.无环:表头的prev指针和表尾的next指针都指向NULL,对链表的访问都是以NULL为终点

3.带表头指针和表尾指针:获取表头和表尾的复杂度都是O(1)

4.带链表长度计数器:len属性记录,获取链表长度O(1)

5.多态:链表结点使用void*指针来保存结点的值,并且可以通过链表结构的三个函数为结点值设置类型特定函数,所以链表可以保存各种不同类型的值

双向链表详解:https://www.cnblogs.com/vic-tory/p/13140779.html

中文网:http://redis.cn/commands.html#list

2.源码解析

// listNode 双端链表节点
typedef struct listNode { // 前置节点
struct listNode *prev; // 后置节点
struct listNode *next; // 节点的值
void *value; } listNode;
// list 双端链表
typedef struct list { // 在c语言中,用结构体的方式来模拟对象是一种常见的手法 // 表头节点
listNode *head; // 表尾节点
listNode *tail; // 节点值复制函数
void *(*dup)(void *ptr); // 节点值释放函数
void(*free)(void *ptr); // 节点值对比函数
int(*match)(void *ptr, void *key); // 链表所包含的节点数量
unsigned long len; } list;
/* 作为宏实现的函数 */
//获取长度
#define listLength(l) ((l)->len)
//获取头节点
#define listFirst(l) ((l)->head)
//获取尾结点
#define listLast(l) ((l)->tail)
//获取前一个结点
#define listPrevNode(n) ((n)->prev)
//获取后一个结点
#define listNextNode(n) ((n)->next)
//获取结点的值 是一个void类型指针
#define listNodeValue(n) ((n)->value) /* 下面3个函数主要用来设置list结构中3个函数指针,参数m为method的意思 */
#define listSetDupMethod(l,m) ((l)->dup = (m))
#define listSetFreeMethod(l,m) ((l)->free = (m))
#define listSetMatchMethod(l,m) ((l)->match = (m)) /* 下面3个函数主要用来获取list结构的单个函数指针 */
#define listGetDupMethod(l) ((l)->dup)
#define listGetFree(l) ((l)->free)
#define listGetMatchMethod(l) ((l)->match)

3.API实现

listCreate函数:创建一个不包含任何结点的新链表

/*
* listCreate 创建一个新的链表
*
* 创建成功返回链表,失败返回 NULL 。
*
* T = O(1)
*/
list *listCreate(void)
{
struct list *list; // 分配内存
if ((list = zmalloc(sizeof(*list))) == NULL)
return NULL;//内存分配失败则返回NULL // 初始化属性
list->head = list->tail = NULL;//空链表
list->len = ;
list->dup = NULL;
list->free = NULL;
list->match = NULL; return list;
}

listAddNodeHead函数:将一个包含给定值的新结点添加到给定链表的表头

/*
* listAddNodeHead 将一个包含有给定值指针 value 的新节点添加到链表的表头
*
* 如果为新节点分配内存出错,那么不执行任何动作,仅返回 NULL
*
* 如果执行成功,返回传入的链表指针
*
* T = O(1)
*/
list *listAddNodeHead(list *list, void *value)
{
listNode *node; // 为节点分配内存
if ((node = zmalloc(sizeof(*node))) == NULL)
return NULL; // 保存值指针
node->value = value; // 添加节点到空链表
if (list->len == ) {
list->head = list->tail = node;
//该结点的前驱和后继都为NULL
node->prev = node->next = NULL;
}
else { // 添加节点到非空链表
node->prev = NULL;
node->next = list->head;
list->head->prev = node;
list->head = node;
} // 更新链表节点数
list->len++; return list;
}

listAddNodeTail函数:将一个包含给定值的新结点插入到给定链表的表尾

/*
* listAddNodeTail 将一个包含有给定值指针 value 的新节点添加到链表的表尾
*
* 如果为新节点分配内存出错,那么不执行任何动作,仅返回 NULL
*
* 如果执行成功,返回传入的链表指针
*
* T = O(1)
*/
list *listAddNodeTail(list *list, void *value)
{
listNode *node; // 为新节点分配内存
if ((node = zmalloc(sizeof(*node))) == NULL)
return NULL; // 保存值指针
node->value = value; // 目标链表为空
if (list->len == ) {
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;
}

listInsertNode函数:将一个给定值的新结点插入到给定结点之前或者之后

/*
* listInsertNode 创建一个包含值 value 的新节点,并将它插入到 old_node 的之前或之后
*
* 如果 after 为 0 ,将新节点插入到 old_node 之前。
* 如果 after 为 1 ,将新节点插入到 old_node 之后。
*
* T = O(1)
*/
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;
}

listDelNode函数:从指定的list中删除给定的结点

/*
* listDelNode 从链表 list 中删除给定节点 node
*
* 对节点私有值(private value of the node)的释放工作由调用者进行。该函数一定会成功.
*
* T = O(1)
*/
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--;
}

listRelease函数:释放给定链表以及链表中所有结点

/*
* listRelease 释放整个链表,以及链表中所有节点, 这个函数不可能会失败.
*
* T = O(N)
*/
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);
}

该函数不仅释放了表结点的内存还释放了表结构的内存

 listGetIterator函数:为给定链表创建一个迭代器

在讲这个函数之前,我们应该先看看链表迭代器的结构:

// listIter 双端链表迭代器
typedef struct listIter { // 当前迭代到的节点
listNode *next; // 迭代的方向
int direction; } listIter;

迭起器只有两个重要的属性:当前迭代到的结点,迭代的方向

下面再看看链表的迭代器创建函数

/*
* listGetIterator 为给定链表创建一个迭代器,
* 之后每次对这个迭代器调用 listNext 都返回被迭代到的链表节点,调用该函数不会失败
*
* direction 参数决定了迭代器的迭代方向:
* AL_START_HEAD :从表头向表尾迭代
* AL_START_TAIL :从表尾想表头迭代
*
* T = O(1)
*/
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;
}

listReleaseIterator函数:释放指定的迭代器

/*
* listReleaseIterator 释放迭代器
*
* T = O(1)
*/
void listReleaseIterator(listIter *iter) {
zfree(iter);
}

listRewind函数和listRewindTail函数:迭代器重新指向表头或者表尾的函数

/*
* 将迭代器的方向设置为 AL_START_HEAD,
* 并将迭代指针重新指向表头节点。
*
* T = O(1)
*/
void listRewind(list *list, listIter *li) {
li->next = list->head;
li->direction = AL_START_HEAD;
} /*
* 将迭代器的方向设置为 AL_START_TAIL,
* 并将迭代指针重新指向表尾节点。
*
* T = O(1)
*/
void listRewindTail(list *list, listIter *li) {
li->next = list->tail;
li->direction = AL_START_TAIL;
}

listNext函数:返回当前迭代器指向的结点

/*
* 返回迭代器当前所指向的节点。
*
* 删除当前节点是允许的,但不能修改链表里的其他节点。
*
* 函数要么返回一个节点,要么返回 NULL,常见的用法是:
*
* iter = listGetIterator(list,<direction>);
* while ((node = listNext(iter)) != NULL) {
* doSomethingWith(listNodeValue(node));
* }
*
* T = O(1)
*/
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;
}

该函数保持了当前结点的下一个结点,避免了当前结点被删除而迭代器无法继续迭代的尴尬情况

 listDup函数:复制整个链表,返回副本

/*
* 复制整个链表。
*
* 复制成功返回输入链表的副本,
* 如果因为内存不足而造成复制失败,返回 NULL 。
*
* 如果链表有设置值复制函数 dup ,那么对值的复制将使用复制函数进行,
* 否则,新节点将和旧节点共享同一个指针。
*
* 无论复制是成功还是失败,输入节点都不会修改。
*
* T = O(N)
*/
list *listDup(list *orig)
{
list *copy;//链表副本
listIter *iter;//链表迭代器
listNode *node;//链表结点 // 创建新的空链表
if ((copy = listCreate()) == NULL)
return NULL;//创建空的链表失败则返回NULL // 设置副本链表的节点值处理函数
copy->dup = orig->dup;
copy->free = orig->free;
copy->match = orig->match; //获取输入链表的迭代器
iter = listGetIterator(orig, AL_START_HEAD); //遍历整个输入链表进行复制
while ((node = listNext(iter)) != NULL) { //副本结点值
void *value; // 存在复制函数
if (copy->dup) { //调用复制函数复制
value = copy->dup(node->value); //复制结果为空,说明复制失败
if (value == NULL) { //复制失败则释放副本链表和迭代器,避免内存泄漏
listRelease(copy);
listReleaseIterator(iter); return NULL;
}
}
//不存在复制函数 则直接指针指向
else
value = node->value; // 将节点添加到副本链表
if (listAddNodeTail(copy, value) == NULL) { //如果不能成功添加,则释放副本链表和迭代器,避免内存泄漏
listRelease(copy);
listReleaseIterator(iter); return NULL;
}
} // 释放迭代器
listReleaseIterator(iter); // 返回副本
return copy;
}

如果复制失败则要注意释放副本链表和迭代器,避免内存泄漏

 listSearchKey函数:查找list中值和key匹配的结点

/*
* 查找链表 list 中值和 key 匹配的节点。
*
* 对比操作由链表的 match 函数负责进行,
* 如果没有设置 match 函数,
* 那么直接通过对比值的指针来决定是否匹配。
*
* 如果匹配成功,那么第一个匹配的节点会被返回。
* 如果没有匹配任何节点,那么返回 NULL 。
*
* T = O(N)
*/
listNode *listSearchKey(list *list, void *key)
{
listIter *iter;//链表迭代器
listNode *node;//链表结点 //获得链表迭代器
iter = listGetIterator(list, AL_START_HEAD); //遍历整个链表查询
while ((node = listNext(iter)) != NULL) { //存在比较函数
if (list->match) { //利用比较函数进行比较
if (list->match(node->value, key)) { //返回目标结点之前释放迭代器空间,避免内存泄漏
listReleaseIterator(iter); return node;
}
}
//不存在比较函数
else {
//直接比较
if (key == node->value) { //返回目标结点之前释放迭代器空间,避免内存泄漏
listReleaseIterator(iter);
// 找到
return node;
}
}
} //返回目标结点之前释放迭代器空间,避免内存泄漏
listReleaseIterator(iter); // 未找到
return NULL;
}

listIndex函数:返回链表在给定索引上的值

/*
* 返回链表在给定索引上的值。
*
* 索引以 0 为起始,也可以是负数, -1 表示链表最后一个节点,诸如此类。
*
* 如果索引超出范围(out of range),返回 NULL 。
*
* T = O(N)
*/
listNode *listIndex(list *list, long index) { listNode *n;//链表结点 /* n不用设置成NULL的原因:
如果索引超出范围,
那肯定是找到表头或者表尾没有找到,
表头的前驱和表尾的后继都是NULL,
所以这里n不用设置为NULL,直接设置也可以*/ // 如果索引为负数,从表尾开始查找
if (index < ) { //变成正数,方便索引
index = (-index) - ; //从尾部开始找
n = list->tail; //寻找 因为从尾部开始找,所以是前驱
while (index-- && n) n = n->prev; } // 如果索引为正数,从表头开始查找
else { //从头部开始找
n = list->head; //寻找 因为从头部开始找,所以是后继
while (index-- && n) n = n->next;
} return n;
}

listRotate函数:取出链表的表尾结点放到表头,成为新的表头结点

/*
* 取出链表的表尾节点,并将它移动到表头,成为新的表头节点。
*
* T = O(1)
*/
void listRotate(list *list) { //表尾结点
listNode *tail = list->tail; //如果链表中只有一个元素,那么表头就是表尾,可以直接返回
if (listLength(list) <= ) return; // 重新设置表尾节点
list->tail = tail->prev;
list->tail->next = NULL; // 插入到表头
list->head->prev = tail;
tail->prev = NULL;
tail->next = list->head;
list->head = tail;
}

Redis系列(五):数据结构List双向链表中基本操作操作命令和源码解析的更多相关文章

  1. Redis系列(四):数据结构String类型中基本操作命令和源码解析

    1.介绍 string类型本质上是char[]数组的封装  中文网:http://www.redis.cn/commands.html#string  2.常用命令 set 命令 set命令的时间复杂 ...

  2. Redis系列(十二):数据结构SortedSet跳跃表中基本操作命令和源码解析

    1.SkipList Redis的sortedSet数据结构是有序不重复的(索引为唯一的,数据(score)却可以重复), 跳表是redis的一个核心组件,也同时被广泛地运用到了各种缓存地实现当中,它 ...

  3. Redis(五):hash/hset/hget 命令源码解析

    Redis作为nosql数据库,kv string型数据的支持是最基础的,但是如果仅有kv的操作,也不至于有redis的成功.(memcache就是个例子) Redis除了string, 还有hash ...

  4. SQL Server 2008空间数据应用系列五:数据表中使用空间数据类型

    原文:SQL Server 2008空间数据应用系列五:数据表中使用空间数据类型 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft SQL Server 2008 R2调测 ...

  5. Scala 深入浅出实战经典 第65讲:Scala中隐式转换内幕揭秘、最佳实践及其在Spark中的应用源码解析

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...

  6. Scala 深入浅出实战经典 第61讲:Scala中隐式参数与隐式转换的联合使用实战详解及其在Spark中的应用源码解析

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载: 百度云盘:http://pan.baidu.com/s/1c0noOt ...

  7. Scala 深入浅出实战经典 第60讲:Scala中隐式参数实战详解以及在Spark中的应用源码解析

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...

  8. Scala 深入浅出实战经典 第48讲:Scala类型约束代码实战及其在Spark中的应用源码解析

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-64讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...

  9. Redis系列(八):数据结构List双向链表中阻塞版本之BLPOP、BRPOP和LINDEX、LINSERT、LRANGE命令详解

    1.BRPOP.BLPOP BLPOP: BLPOP 是阻塞式列表的弹出原语. 它是命令 LPOP 的阻塞版本,这是因为当给定列表内没有任何元素可供弹出的时候, 连接将被 BLPOP 命令阻塞. 当给 ...

随机推荐

  1. 网络编程杂谈之TCP协议

    TCP协议属于网络分层中的传输层,传输层作用的就是建立端口与端口的通信,而其下一层网络层的主要作用是建立"主机到主机"的通信,所以在我们日常进行网络编程时只要确定主机和端口,就能实 ...

  2. 理解session及微信小程序使用session

    session介绍 由于Http是无状态的协议,所以服务端需要记录用户的状态时,就需要某种机制来识别具体的用户,实现这个机制的方式就是session. 典型的场景比如购物车,当你点击下单按钮时,由于H ...

  3. 如何理解Java中的自动拆箱和自动装箱?

    小伟刚毕业时面的第一家公司就被面试官给问住了... 如何理解Java中的自动拆箱和自动装箱? 自动拆箱?自动装箱?什么鬼,听都没听过啊,这...这..知识盲区... 回到家后小伟赶紧查资料,我透,这不 ...

  4. JAVA课程学习感想

    JAVA课程学习感想 在学习JAVA之前,我们学习了C语言,汇编语言,数据结构等等.虽然学习了这些,但对于JAVA来说,学习起来不是那么容易,所有的计算机语言有相似的地方,但他们更有不同的地方.对我来 ...

  5. 前端自动化构建之gulp

    前言 之前学完html的基础后就去学js框架了,每次都是用脚手架搭好的文件,在无形中体验了一波前端自动化带来的方便.然后前一段时间才开始学习前端自动化. 基本介绍 gulp说得简单一点就是一个自动化把 ...

  6. 学习使用pyquery解析器爬小说

    一.背景:个人喜欢在网上看小说,但是,在浏览器中阅读小说不是很方便,喜欢找到小说的txt版下载到手机上阅读,但是有些小说不太好找txt版本,考虑自己从网页上爬一爬,自己搞定小说的txt版本.正好学习一 ...

  7. PAT 1036 Boys vs Girls (25分) 比大小而已

    题目 This time you are asked to tell the difference between the lowest grade of all the male students ...

  8. Rocket - debug - TLDebugModuleInner - DMSTATUS

    https://mp.weixin.qq.com/s/GyGriFyeq_7Z3xOjKn56Mg 简单介绍TLDebugModuleInner中DMSTATUS寄存器的实现. 1. DMSTATUS ...

  9. Chisel3 - util - OneHot

    https://mp.weixin.qq.com/s/Jsy8P3m9W2EYKwneGVekiw   独热码相关的电路生成器.   参考链接: https://github.com/freechip ...

  10. 程序员的脑袋系列---利用ffmpeg命令提取音频

    今日各大播放器的版权控制越来越严格.导致很多歌曲无法听,但是MV却可以听.这样很蛋疼有木有? 然而,我们可以利用ffmpeg工具提取MV的音频,比如做成MP3格式,这样就可以听了.--哈哈(邪恶地笑) ...