Redis 设计与实现(第三章) -- 链表adlist

概述



1.链表介绍

2.链表API

链表介绍

链表在Redis中的应用非常广泛,比如列表键list的底层实现就是使用的链表。除了列表键外,Redis的发布与订阅、慢查询、监视器等功能也用到了链表,Redis服务器本身还使用了链表来保存客户端连接状态,以后使用链表来构建客户端输出缓冲区。

链表在Redis的数据结构如下:

typedef struct listNode {
    struct listNode *prev;  //前一个节点
    struct listNode *next;  //后一个节点,可以看出链表为双向链表
    void *value;  //节点值
} listNode;

其实可以通过多个listNode链接起来,就是一个双向链表,但是使用list结构来持有链表会更方便,如下:

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;

Redis中链表的特点:

1.双向,有prev和next指针指向前/后一个节点;

2.无环,header的prev和tail的next都指向null;

3.带表头和表尾指针,快速获取表头/尾;

4.带链表长度值;

5.多态,通过void* 来保存节点值,可以通过函数为节点值设置类型特定函数,所以链表能够保存各种不同类型的值。

链表API

添加头部节点,和基本链表操作类似

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

search节点

listNode *listSearchKey(list *list, void *key)
{
    listIter *iter;
    listNode *node;

    iter = listGetIterator(list, AL_START_HEAD); //获取遍历器
    while((node = listNext(iter)) != NULL) {  //遍历节点
        if (list->match) { //match函数不为空,即设置了链表的值比较函数
            if (list->match(node->value, key)) {  //根据match函数来比较
                listReleaseIterator(iter);
                return node;
            }
        } else {  //没设置,直接比较
            if (key == node->value) {
                listReleaseIterator(iter);
                return node;
            }
        }
    }
    listReleaseIterator(iter);
    return NULL;
}

遍历器

typedef struct listIter {  //设置一个listIter遍历器
    listNode *next;
    int direction; //direction值为AL_START_HEAD(0)头部节点开始,AL_START_TAIL(1)尾部节点开始
} listIter;

获取迭代器的方法:

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; //设置next节点为head
    else
        iter->next = list->tail;
    iter->direction = direction;
    return iter;
}

然后通过next函数通过迭代器遍历:

/* Return the next element of an iterator.
 * It‘s valid to remove the currently returned element using
 * listDelNode(), but not to remove other elements.
 *
 * The function returns a pointer to the next element of the list,
 * or NULL if there are no more elements, so the classical usage patter
 * is:
 *使用方法
 * iter = listGetIterator(list,<direction>);
 * while ((node = listNext(iter)) != NULL) {
 *     doSomethingWith(listNodeValue(node));
 * }
 *
 * */
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;
}
时间: 2024-12-12 00:12:38

Redis 设计与实现(第三章) -- 链表adlist的相关文章

当代码遇到数理逻辑——面向对象设计与构造第三章总结

在面向对象课程中的第三章,我尝试了基于JML语言的规格化设计,按照AppRunner中的接口文件实现了Path类和PathContainer, Graph, RailWaySystem迭代类.JML语言是一种规格化语言,完全建立于数理逻辑上,既能够为开发者实现类与方法时提供准确的功能参考,也能够在特定工具支持下充当assert的功能和辅助自动生成测试样例. 本篇博客将从以下几方面对第三章进行总结: JML的基本语法与工具链 基于JmlUnitNg的自动测试方法尝试 三次作业架构 程序Bug分析

Redis 设计与实现(第九章) -- 数据库

概述 1.数据库结构 2.数据库键空间 3.键生存时间 4.持久化对过期键处理 5.数据库通知 1.数据库结构 Redis服务器将所有server状态都保存在数据结构中的db数组,服务器会根据dbnum来决定创建多个个数据库,默认为16个. struct redisServer { //数据结构里面有很多属性,这里只取了相关的两个来说明 /* General */ redisDb *db; int dbnum; }redisServer; 创建db后,如下所示: 同样的在redisClient的

大型分布式架构设计与实现-第三章互联网安全架构

本章首先介绍了一些常见的Web攻击手段. 1.XSS攻击(Cross Sites Scripting),指跨站脚本攻击.攻击者在网页中嵌入恶意脚本程序,当用户打开该网页,恶意程序在浏览器执行,会盗取用户名密码,cookie,下载执行病毒木马程序,甚至是获取客户端admin权限等. 2.CSRF攻击(Cross Site Request Forgery),指跨站请求伪造.攻击者伪装成站点内受信任的用户进行恶意的发邮件,发短信,进行交易等,甚至盗取你的账号. 3.SQL注入攻击.通过把SQL命令伪装

《linux内核设计与实现》读书笔记第三章

第3章 进程管理 3.1 进程 1.进程 进程就是处于执行期的程序. 进程包括: 可执行程序代码 打开的文件 挂起的信号 内核内部数据 处理器状态 一个或多个具有内存映射的内存地址空间 一个或多个执行线程 用来存放全局变量的数据段 …… 实际上,进程就是正在执行的程序代码的实时结果 2.执行线程 简称线程,是在进程中活动的对象. 每个线程都拥有一个独立的程序计数器.进程栈和一组进程寄存器. 内核调度的对象是线程,而不是进程. 进程提供两种虚拟机制: 虚拟处理器和虚拟内存. 在线程之间可以共享虚拟

《操作系统精髓与设计原理》习题第三章

第三章习题 3.10.1关键术语 阻塞态:进程在某些事件发生之前不能执行,等待这种事件发生的状态. 退出态:操作系统从可执行进程组中释放出的进程,自身停止了,或者因某种原因被取消. 内核态:某些指令只能在特权状态下执行,而这种特权状态称为内核态. 子进程:由一个进程创建的进程,该进程的终止受父进程的影响. 中断:由外部事件引发进程挂起,CPU转而去处理发起中断的事件,并处理结束后恢复进程的执行. 模式切换:CPU由用户态和核心态之间相互切换. 新建态:进程创建时仅仅创建了对应的进程控制块而没有在

MiS603开发板 第三章 多路分频器设计

作者:MiS603开发团队 日期:20150911 公司:南京米联电子科技有限公司 论坛:www.osrc.cn 网址:www.milinker.com 网店:http://osrc.taobao.com EAT博客:http://blog.chinaaet.com/whilebreak 博客园:http://www.cnblogs.com/milinker/ MiS603开发板 第三章 多路分频器设计 设计思想:利用FPGA内部的逻辑单元对FPGA输入的50MHZ高频时钟信号进行计数分频,得到

Linux内核设计与实现读书笔记——第三章

Linux内核设计与实现读书笔记——第三章 进程管理 20135111李光豫 3.1进程 1.进程即处于执行期的程序,并不局限于一个可执行的代码,是处于执行期程序以及其相关资源的总称. 2.Linux系统中,对于进程和线程并没有明显的区分,线程是一种特殊的进程. 3.Linux系统中,常用fork()进程创建子进程.调用fork()进程的成之为其子进程的父进程. 4.fork()继承实际上由clone()系统调用实现.最后通过exit()退出执行. 3.2任务描述符及任务结构 1.任务队列实质上

第三章:更新异常与规范化设计

前言 在前两章中,主要讲了ER建模和关系建模.在具体分析如何用数据库管理软件RDBMS(Relational Database Management System)实现这些关系前,我想有必要思考下面这个问题: 问什么要这么麻烦?为什么又是ER建模又是关系建模的? 本章的出发点就是回答这个问题.然而某种程度上,也是回答另一个本质性的问题:为什么要有数据库? 更新异常 数据库的四大操作:增,删,改,查中,除了查,其他三个都可归为更新操作.而总的来说,ER建模和关系建模的目的,就是为了避免因大量冗余数

《Linux内核设计与实现》第三章学习笔记

第三章  进程管理 姓名:王玮怡  学号:20135116 一.进程 1.进程的含义 进程是处于执行期的程序以及相关资源的总称,程序本身并不是进程,实际上就是正在执行的代码的实时结果.Linux内核通常把进程也叫“任务”. 2.线程的含义 执行线程简称线程,是在进程中互动的对象.内核调度的对象是线程而不是进程.Linux系统不区分线程和进程,线程只是一种特殊的进程. 3.进程的执行过程 (1)clone()调用fork(),通过复制一个现有进程来创建一个全新的进程,进程开始存活.其中调用fork

linux及安全《Linux内核设计与实现》第三章——20135227黄晓妍

第三章 (由于linux不区分进程和线程,所以它们在linux中被称为task,也叫任务) 总结:本章主要包括进程以及线程的概念和定义,Linux内核如何管理每个进程,他们在内核中如何被列举,如何创建,最终如何消亡.操作系统存在的意义在于运行用户程序,进程管理是所有操作系统的心脏所在. 3.1进程 进程是处于执行期的程序,是正在执行的程序代码的实时结果.但不仅局限于一段可执行的代码,还包括其他资源(打开的文件,挂起的信号,内核内部数据,处理器的状态,一个或者多个内存映射的内存地址空间,一个或者多