Redis 源码解析之通用双向链表(adlist)

news2024/11/13 9:21:10

概述

Redis源码中广泛使用 adlist(A generic doubly linked list),作为一种通用的双向链表,用于简单的数据集合操作。adlist提供了基本的增删改查能力,并支持用户自定义深拷贝、释放和匹配操作来维护数据集合中的泛化数据 value

adlist 的数据结构

  1. 链表节点 listNode, 作为双向链表, prevnext 指针分别指向前序和后序节点。void* 指针类型的 value 用于存放泛化的数据类型(如果数据类型的 size 小于 sizeof(void*), 则可直接存放在 value中。 否则 value 存放指向该泛化类型的指针)。
// in adlist.h
typedef struct listNode {
    struct listNode *prev;
    struct listNode *next;
    void *value;
} listNode;
  1. 链表迭代器 listIter, 其中 next 指针指向下一次访问的链表节点。direction 标识当前迭代器的方向是 AL_START_HEAD(从头到尾遍历) 还是 AL_START_TAIL(从尾到头遍历)
// in adlist.h
typedef struct listIter {
    listNode *next;
    int direction;
} listIter;

/* Directions for iterators */
#define AL_START_HEAD 0
#define AL_START_TAIL 1
  1. 双向链表结构 list。 其中, head 和 tail 指针分别指向链表的首节点和尾节点。len 记录当前链表的长度。函数指针 dupfree 和 match 分别代表业务注册的对泛化类型 value 进行深拷贝,释放和匹配操作的函数。(如果没有注册 dup, 则默认进行浅拷贝。 如果没有注册 free, 则不对 value 进行释放。如果没有注册 match 则直接比较 value 的字面值)
// in adlist.h
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;

adlist 的基本操作

  1. 创建listCreate 初始化相关字段为零值。可以通过 listSetDupMethod, listSetFreeMethodlistSetMatchMethod来注册该链表泛化类型 value 的 dupfree 和 match 函数。
/* Create a new list. The created list can be freed with
 * listRelease(), but private value of every node need to be freed
 * by the user before to call listRelease(), or by setting a free method using
 * listSetFreeMethod.
 *
 * On error, NULL is returned. Otherwise the pointer to the new list. */
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))

image

  1. 在链表首插入新节点listAddNodeHead
  • 在空链表插入新节点: 为 value 创建新节点,并让 list 的 head 和 tail 都指向新节点。

    image

  • 在非空链表插入新节点:
    (1) 将新节点的 next 指向当前首节点(当前首节点将成为第二节点, 将会是新节点的后继节点)
    (2) 将当前节点的 prev 指向新节点, 新节点作为新的首节点将成为原首节点的前驱节点。
    (3) 将 head 从原本指向旧的首节点改为指向新节点, 将新节点作为链表首。
    (4) 链表总计数加一

    image

/* Add a new node to the list, to head, containing the specified 'value'
 * pointer as value.
 *
 * On error, NULL is returned and no operation is performed (i.e. the
 * list remains unaltered).
 * On success the 'list' pointer you pass to the function is returned. */
list *listAddNodeHead(list *list, void *value)
{
    listNode *node;

    if ((node = zmalloc(sizeof(*node))) == NULL)
        return NULL;
    node->value = value;
    listLinkNodeHead(list, node);
    return list;
}

/*
 * Add a node that has already been allocated to the head of list
 */
void listLinkNodeHead(list* list, listNode *node) {
    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++;
}
  1. 在链表尾插入新节点listAddNodeTail
  • 在空链表插入新节点: 逻辑与 listAddNodeHead 实现一致。
  • 在非空链表插入新节点:
    (1) 将新节点的 prev 指向当前首节点(当前尾节点将成为倒数第二节点, 将会是新节点的前驱节点)
    (2) 将当前节点的 next 指向新节点, 新节点作为新的尾节点将成为原尾节点的后继节点。
    (3) 将 tail 从原本指向旧的尾节点改为指向新节点, 将新节点作为链表尾。
    (4) 链表总计数加一

image

/* Add a new node to the list, to tail, containing the specified 'value'
 * pointer as value.
 *
 * On error, NULL is returned and no operation is performed (i.e. the
 * list remains unaltered).
 * On success the 'list' pointer you pass to the function is returned. */
list *listAddNodeTail(list *list, void *value)
{
    listNode *node;

    if ((node = zmalloc(sizeof(*node))) == NULL)
        return NULL;
    node->value = value;
    listLinkNodeTail(list, node);
    return list;
}

/*
 * Add a node that has already been allocated to the tail of list
 */
void listLinkNodeTail(list *list, listNode *node) {
    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++;
}
  1. 在链表指定位置插入 valuelistInsertNode。如果 after 为非零, 则将新节点作为 old_node 后继节点。否则,新节点作为 old_node 前驱节点。下图以 after 为非零作为例子, 描述了这部分的代码逻辑。
    (1) 将新节点的 prev 指向 old_node(新节点插入在 old_node 之后);
    (2) 将新节点的 next 指向 old_node 的后继节点(old_node 的后继节点将成为新节点的后继节点);
    (3) 将 old_node 的 next 指向新节点;
    (4) 将新节点的后继节点的 prev指向新节点(old_node的原后继节点现在成为了新节点的后继节点) 。
    (5) 链表总计数加一

image

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;
}
  1. 删除链表指定节点listDelNode。 下图以删除中间节点为例,展示了删除的流程。
    (1) 待删除节点的前驱节点的 next 指向待删除节点的后继节点;
    (2) 待删除节点的后继节点的 prev 指向待删除节点的前驱节点;
    (3) 待删除节点的 next 和 prev 都置为 NULL;
    (4) 链表总计数减一
    (5) 如果有注册 free 函数,则用 free 函数释放待删除节点的 value。然后释放待删除节点。

image


/* Remove the specified node from the specified list.
 * The node is freed. If free callback is provided the value is freed as well.
 *
 * This function can't fail. */
void listDelNode(list *list, listNode *node)
{
    listUnlinkNode(list, node);
    if (list->free) list->free(node->value);
    zfree(node);
}

/*
 * Remove the specified node from the list without freeing it.
 */
void listUnlinkNode(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;

    node->next = NULL;
    node->prev = NULL;

    list->len--;
}

5.链表的 Join 操作listJoin 在链表l的末尾添加列表o的所有元素。 下图以两个链表都不为 NULL 的场景为例。
(1) o 的首部节点的 prev 指向 l 的尾部节点;
(2) l 的尾部节点的 next 指向 o 的首部节点(1,2 步将两个链表链接起来);
(3) l 的 tail 指向 o 的 tail(o 的 tail作为新链表的尾部);
(4) l 链表总计数加一;
(5) (6) 清空 o 链表的信息;

image

/* Add all the elements of the list 'o' at the end of the
 * list 'l'. The list 'other' remains empty but otherwise valid. */
void listJoin(list *l, list *o) {
    if (o->len == 0) return;

    o->head->prev = l->tail;

    if (l->tail)
        l->tail->next = o->head;
    else
        l->head = o->head;

    l->tail = o->tail;
    l->len += o->len;

    /* Setup other as an empty list. */
    o->head = o->tail = NULL;
    o->len = 0;
}
  1. 其他函数: 其他函数实现较为简单,这里简单罗列一下,感兴趣的可以去看下源码。
// 获取 list 的迭代器
listIter *listGetIterator(list *list, int direction);
// 返回迭代器的下一个元素,并将迭代器移动一位。如果已遍历完成, 则返回 NULL
listNode *listNext(listIter *iter);
// 释放迭代器资源
void listReleaseIterator(listIter *iter);

// 拷贝链表
list *listDup(list *orig);

// 在链表中查找与 key 匹配的 value 所在的第一个节点。
// 如果不存在,则返回 NULL。
// 匹配操作由 list->match 函数提供。
// 如果没有注册 match 函数, 则直接比较 key 是否与 value 相等。
listNode *listSearchKey(list *list, void *key);

// 返回指定的索引的元素。 如果超过了链表范围, 则返回 NULL。
// 正整数表示从首部开始计算。
// 0 表示第一个元素, 1 表示第二个元素, 以此类推。
// 负整数表示从尾部开始计算。
// -1 表示倒数第一个元素, -2 表示倒数第二个元素,以此类推。
listNode *listIndex(list *list, long index);

// 返回链表初始化的正向迭代器
void listRewind(list *list, listIter *li);
// 返回链表初始化的反向迭代器
void listRewindTail(list *list, listIter *li);

// 将链表尾部节点移到首部
void listRotateTailToHead(list *list);
// 将链表首部节点移到尾部
void listRotateHeadToTail(list *list);

// 用 value 初始化节点
void listInitNode(listNode *node, void *value);

CPP 复制 全屏

adlist 的使用 demo 

git@github.com:younglionwell/redis-adlist-example.git

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/822041.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

我的4周年创作纪念日

机缘 今天是2023年8月1日,工作四年了,记录博客也四年了。 2019年,我硕士毕业入职到了这家公司,当时培训的资料有一句话说:网络通信100Mbps是串口通信的是串口通信的10倍,我当时就好奇是怎么算出来的&…

springboot整合mybatis分页(使用pagehelper 分页插件)-- 学习若依系统

学习文档&#xff08;参考若依系统&#xff09; 若依的文档&#xff1a;http://doc.ruoyi.vip/ruoyi-vue/document/htsc.html#%E5%88%86%E9%A1%B5%E5%AE%9E%E7%8E%B0 就不从零搭建springboot项目了&#xff0c;直接在自己的项目基础上引入。 1、引入的依赖 <!-- pagehel…

matlab使用教程(7)—基本画图函数

1.创建绘图 plot 函数具有不同的形式&#xff0c;具体取决于输入参数。 • 如果 y 是向量&#xff0c; plot(y) 会生成 y 元素与 y 元素索引的分段线图。 • 如果有两个向量被指定为参数&#xff0c; plot(x,y) 会生成 y 对 x 的图形。 使用冒号运算符创建从 0 至 2…

ISO-15031/ISO-15765 诊断说明

注&#xff1a;15765诊断可参考15031&#xff0c;两者诊断逻辑相同 1: ISO15031 目录说明 ISO15031-1: 这里边介绍的是一般信息和用例定义&#xff1b; ISO15031-2: 术语、定义、缩写词和首字母缩写词[技术报告] ISO15031-3: 这里边主要介绍了诊断连接器及相关电路&#xff1…

掌握Python的X篇_17_循环语句(while;for var in ;range)

文章目录 1. 为什么需要循环2. while循环3. for...in循环4. range函数 1. 为什么需要循环 循环语句方便我们做重复的事情&#xff0c;比如&#xff1a; for i in range (0,3):print("重要的事情说三遍")运行效果如下&#xff1a; Python中有while循环和for循环两…

IDEA中Git面板操作介绍 变基、合并、提取、拉取、签出

IDEA中Git面板操作介绍 变基、合并、提取、拉取、签出 面板介绍 变基、合并 提取、拉取 签出、Checkout 面板介绍 如图&#xff0c;在IDEA的Git面板中&#xff0c;仓库会分为本地仓库和远程仓库&#xff0c;代码仓库里面放的是各个分支。 分支前面的书签&#x1f516;标志…

基于总线加锁和缓存锁(CPU实现原子操作的两种方式)

总线锁 总线锁就是使用处理器提供的一个 LOCK&#xff03;信号&#xff0c;当一个处理器在总线上输出此信号时&#xff0c;其他处理器的请求将被阻塞住&#xff0c;那么该处理器可以独占共享内存。 CPU和内存之间的通信被锁&#xff01;&#xff01; 如果多个 处 理器同 时对 …

【数据预测】基于白鲸优化算法BWO的VMD-KELM光伏发电功率预测 短期功率预测【Matlab代码#54】

文章目录 【可更换其他算法&#xff0c;获取资源请见文章第6节&#xff1a;资源获取】1. 白鲸优化算法BWO2. 变分模态分解VMD3. 核极限学习机KELM4. 部分代码展示5. 仿真结果展示6. 资源获取 【可更换其他算法&#xff0c;获取资源请见文章第6节&#xff1a;资源获取】 1. 白鲸…

Vue 2.x 项目升级到 Vue 3详细指南【总结版】

文章目录 0.前言1.升级教程1.1. 升级 Vue CLI&#xff1a;1.2. 安装 Vue 3&#xff1a;1.3. 更新 Vue 组件&#xff1a;1.4. 迁移全局 API&#xff1a;1.5. 迁移路由和状态管理器&#xff1a;1.6. 迁移 TypeScript&#xff1a;1.7. 迁移测试代码&#xff1a; 2.迁移总结2.0. 这…

C++类和对象(下部曲)

构造函数 1 构造函数体赋值 在创建对象时&#xff0c;编译器通过调用构造函数&#xff0c;给对象中各个成员变量一个合适的初始值 虽然对象中已经有了一个初始值&#xff0c;但是不能将其称为对对象中成员变量的初始化 构造函数体中的语句只能将其称为赋初值&#xff0c;而…

十大排序|十大排序

稳定排序&#xff1a;冒泡排序、插入排序、归并排序、基数排序、桶排序 不稳定排序&#xff1a;选择排序、快速排序、希尔排序、堆排序 二、插入排序&#xff1a; 代码&#xff1a; #include<iostream> #include<cstdio> #include<stdlib.h> #include<ve…

真机搭建中小网络

这是b站上的一个视频&#xff0c;演示了如何搭建一个典型的中小网络&#xff0c;供企业使用 一、上行端口&#xff1a;上行端口就是连接汇聚或者核心层的口&#xff0c;或者是出广域网互联网的口。也可理解成上传数据的端口。 二、下行端口&#xff1a;连接数据线进行下载的端…

Vue源码学习 - 虚拟Dom 和 diff算法

目录 前言一、认识虚拟DOM用 JS 对象模拟 DOM 结构用JS对象模拟DOM节点的好处为什么要使用虚拟 DOM 呢&#xff1f;虚拟Dom 和 diff算法的关系 二、认识diff算法diff算法的优化key的作用diff算法 在什么时候执行&#xff1f; 三、深入diff算法源码patch 函数sameVnode 函数patc…

简要介绍 | 生成模型的演进:从自编码器(AE)到变分自编码器(VAE)和生成对抗网络(GAN),再到扩散模型

注1:本文系“简要介绍”系列之一,仅从概念上对生成模型(包括AE, VAE, GAN,以及扩散模型)进行非常简要的介绍,不适合用于深入和详细的了解。 生成模型的演进:从自编码器(AE)到变分自编码器(VAE)和生成对抗网络(GAN),再到扩散模型 一、背景介绍 生成模型在机器学习领域…

【Linux后端开发】poll/epoll多路转接IO服务器

目录 一、poll原理 二、poll实现多路转接IO服务器 三、epoll函数接口 四、epoll的工作原理 五、epoll实现多路转接IO服务器 一、poll原理 poll函数接口 #include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout);// pollfd结构 struct pollfd …

搜索二叉树_SearchBinaryTree

目录 搜索二叉树的原理 搜索二叉树的搜索时间复杂度 二叉搜索树实现_key 模型 节点 构造函数 查找 中序遍历 插入 循环 递归 删除 循环 1.删除叶子节点 2.删除有一个孩子的节点 3.左右孩子都不为空 递归 析构函数 拷贝构造 operator key_value 模型 节点 …

JDBC-笔记

JDBC 1. JDBC介绍 JDBC&#xff08;Java Database Connectivity&#xff09;是一种用于连接和操作数据库的 Java API。 通过Java操作数据库的流程 第一步&#xff1a;编写Java代码 第二步&#xff1a;Java代码将SQL发送到MySQL服务端 第三步&#xff1a;MySQL服务端接收到SQ…

ems

【python爬虫】邮政包裹物流查询 目标网站 ems 邮政快递包裹查询: https://www.ems.com.cn/ 截图 接口预览 getPic请求滑动验证码的背景图片和滑块图片&#xff0c;返回的是base64编码的图片 getLogisticsTestFlag发送验证码的验证信息 xpos为滑动的距离&#xff0c;本站没…

CUDA编译器环境配置篇

cuda教程目录 第一章 指针篇 第二章 CUDA原理篇 第三章 CUDA编译器环境配置篇 第四章 kernel函数基础篇 第五章 kernel索引(index)篇 第六章 kenel矩阵计算实战篇 第七章 kenel实战强化篇 第八章 CUDA内存应用与性能优化篇 第九章 CUDA原子(atomic)实战篇 第十章 CUDA流(strea…

CHI中的System Debug, Trace, and Monitoring

Data Source indication □ Read request的completer&#xff0c;可以在CompData, DataSepResp, SnpRespData, and SnpRespDataPtl response中的datasource域段中指定data的来源&#xff1b;即使响应中带有错误&#xff0c;该datasource也是有效的&#xff1b; □ 该域段也可复…