Redis的设计与实现(2)-链表
链表在 Redis 中的应用非常广泛, 比如列表键的底层实现之一就是链表: 当一个列表键包含了数量比较多的元素, 又或者列表中包含的元素都是比较长的字符串时, Redis 就会使用链表作为列表键的底层实现.
除了链表键之外, 发布与订阅, 慢查询, 监视器等功能也用到了链表, Redis 服务器还使用链表保存多个客户端的状态信息, 以及使用链表来构建客户端输出缓冲区(output buffer).
1. 链表的定义
每个链表节点使用一个 adlist.h/listNode 结构来表示:
typedef struct listNode {
// 前置节点
struct listNode *prev;
// 后置节点
struct listNode *next;
// 节点的值
void *value;
} listNode;
使用 adlist.h/list 来操作链表会更方便
typedef struct list {
// 表头节点
listNode *head;
// 表尾节点
listNode *tail;
// 链表所包含的节点数量
unsigned long len;
// 节点值复制函数
void *(*dup)(void *ptr);
// 节点值释放函数
void (*free)(void *ptr);
// 节点值对比函数
int (*match)(void *ptr, void *key);
} list;
可见, 它是一个双向链表.
head, tail 和 len 分别是表头表尾指针和节点数量, 而 dup, free 和 match 则是用于实现多态链表所需的类型特定函数:
- dup 函数用于复制链表节点所保存的值;
- free 函数用于释放链表节点所保存的值;
- match 函数则用于对比链表节点所保存的值和另一个输入值是否相等.
(有点面向对象的味道)
2. 链表的API
| 函数 | 作用 | 时间复杂度 |
|---|---|---|
| listSetDupMethod | 将给定的函数设置为链表的节点值复制函数 | O(1) |
| listGetDupMethod | 返回链表当前正在使用的节点值复制函数 | 复制函数可以通过链表的 dup 属性直接获得, O(1) |
| listSetFreeMethod | 将给定的函数设置为链表的节点值释放函数 | O(1) |
| listGetFree | 返回链表当前正在使用的节点值释放函数 | 释放函数可以通过链表的 free属性直接获得, O(1) |
| listSetMatchMethod | 将给定的函数设置为链表的节点值对比函数 | O(1) |
| listGetMatchMethod | 返回链表当前正在使用的节点值对比函数 | 对比函数可以通过链表的 `match` 属性直接获得, O(1) |
| listLength | 返回链表的长度(包含了多少个节点) | 链表长度可以通过链表的 `len` 属性直接获得, O(1) |
| listFirst | 返回链表的表头节点 | 表头节点可以通过链表的 `head` 属性直接获得, O(1) |
| listLast | 返回链表的表尾节点 | 表尾节点可以通过链表的 tail 属性直接获得, O(1) |
| listPrevNode | 返回给定节点的前置节点 | 前置节点可以通过节点的 prev 属性直接获得, O(1) |
| listNextNode | 返回给定节点的后置节点 | 后置节点可以通过节点的 next 属性直接获得, O(1) |
| listNodeValue | 返回给定节点目前正在保存的值 | 节点值可以通过节点的 value 属性直接获得, O(1) |
| listCreate | 创建一个不包含任何节点的新链表 | O(1) |
| listAddNodeHead | 将一个包含给定值的新节点添加到给定链表的表头 | O(1) |
| listAddNodeTail | 将一个包含给定值的新节点添加到给定链表的表尾 | O(1) |
| listInsertNode | 将一个包含给定值的新节点添加到给定节点的之前或者之后 | O(1) |
| listSearchKey | 查找并返回链表中包含给定值的节点 | O(N), N 为链表长度 |
| listIndex | 返回链表在给定索引上的节点 | O(N), N 为链表长度 |
| listDelNode | 从链表中删除给定节点 | O(1) |
| listRotate | 将链表的表尾节点弹出, 然后将被弹出的节点插入到链表的表头, 成为新的表头节点 | O(1) |
| listDup | 复制一个给定链表的副本 | O(N), N 为链表长度 |
| listRelease | 释放给定链表, 以及链表中的所有节点 | O(N), N 为链表长度 |
3. 总结
双端: 节点带有 prev 和 next 指针, 获取其前置和后置节点的复杂度都是 O(1);
无环: 表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL, 对链表的访问以 NULL 为终点;
带表头指针和表尾指针: 通过 list 结构的 head 指针和 tail 指针, 获取表头和表尾节点的复杂度为 O(1);
带链表长度计数器: 通过 list 结构的 len 属性, 程序获取链表中节点数量的复杂度为 O(1);
多态: 链表节点使用 void* 指针来保存节点值, 并且可以通过 list 结构的 dup, free, match 三个属性为节点值设置类型特定函数, 可以用于保存各种不同类型的值.
4. 重点回顾
- 链表被广泛用于实现 Redis 的各种功能, 比如列表键, 发布与订阅, 慢查询, 监视器, 等等;
- 每个链表节点由一个 listNode 结构来表示, 每个节点都有一个指向前置节点和后置节点的指针, 所以 Redis 的链表实现是双端链表;
- 每个链表使用一个 list 结构来表示, 这个结构带有表头节点指针, 表尾节点指针, 以及链表长度等信息;
- 因为链表表头节点的前置节点和表尾节点的后置节点都指向 NULL , 所以 Redis 的链表实现是无环链表;
- 通过为链表设置不同的类型特定函数, Redis 的链表可以用于保存各种不同类型的值.
文章来源于本人博客,发布于 2018-04-12,原文链接:https://imlht.com/archives/122/
Redis的设计与实现(2)-链表的更多相关文章
- [Redis]Redis的设计与实现-链表/字典/跳跃表
redis的设计与实现:1.假如有一个用户关系模块,要实现一个共同关注功能,计算出两个用户关注了哪些相同的用户,本质上是计算两个用户关注集合的交集,如果使用关系数据库,需要对两个数据表执行join操作 ...
- Redis --> Redis架构设计
Redis架构设计 一.前言 Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件. 它支持多种类型的数据结构,如 字符串(strings), 散列 ...
- Redis核心设计原理(深入底层C源码)
Redis 基本特性 1. 非关系型的键值对数据库,可以根据键以O(1) 的时间复杂度取出或插入关联值 2. Redis 的数据是存在内存中的 3. 键值对中键的类型可以是字符串,整型,浮点型等,且键 ...
- 11.Redis缓存设计
11.Redis缓存设计11.1 缓存的收益和成本11.2 缓存更新策略11.3 缓存粒度控制11.4 穿透优化11.5 无底洞优化11.6 雪崩优化11.7 热点key重建优化11.8 本章重点回顾
- Redis缓存设计及常见问题
Redis缓存设计及常见问题 缓存能够有效地加速应用的读写速度,同时也可以降低后端负载,对日常应用的开发至关重要.下面会介绍缓存使 用技巧和设计方案,包含如下内容:缓存的收益和成本分析.缓存更新策略的 ...
- Python 基于python+mysql浅谈redis缓存设计与数据库关联数据处理
基于python+mysql浅谈redis缓存设计与数据库关联数据处理 by:授客 QQ:1033553122 测试环境 redis-3.0.7 CentOS 6.5-x86_64 python 3 ...
- redis数据库设计(转)
原文:http://segmentfault.com/q/1010000000316112 redis是什么 redis就是一个存储key-value键值对的仓库,如何使用redis在于如何理解你需要 ...
- 17 redis -key设计原则
书签系统 create table book ( bookid int, title char(20) )engine myisam charset utf8; insert into book va ...
- 细说分布式Redis架构设计和踩过的那些坑
细说分布式Redis架构设计和踩过的那些坑_redis 分布式_ redis 分布式锁_分布式缓存redis 细说分布式Redis架构设计和踩过的那些坑
- Redis设计与实现 (二): 链表
Redis实现为双链表结构, 列表键的底层实现之一就是链表, 发布与订阅, 慢查询, 监视器等功能都用到了链表. Redis本身也使用链表维持多个客户端. 节点定义, 位于 adlist.h/lis ...
随机推荐
- 创建SpringBoot项目,在yml中配置数据库, driver-class-name: com.mysql.cj.jdbc.Driver标红报错解决方式
一.报错原因 com.mysql.cj.jdbc.Driver一直标红报错,原因在于pom.xml中mysql包没有下载下来,或者在创建项目的时候有问题 二.解决方案 在pom.xml添加 <d ...
- SRAM 测试总结
SoC随着工艺进步设计复杂度增加,embeded sram也越来越多.在40nm SoC产品Sram一般在20Mbits左右,当工艺发展到28nm时Sram就增加到100Mbits.如果考虑AI产品, ...
- mybatis中useGeneratedKeys用法--插入数据库后获取主键值
前言:今天无意在mapper文件中看到useGeneratedKeys这个词,好奇就查了下,发现能解决我之前插入有外键表数据时,这个外键获取繁琐的问题,于是学习敲DEMO记录 在项目中经常需要获 ...
- Nginx配置https并监听80端口重定向到443
1.进入nginx安装目录,进入config文件夹编辑nginx.conf文件 vim nginx.conf 配置端口 443 listen 443 http2 ssl default_server ...
- 重学C++ (一)基础回顾
由于C++基础非常重要,所以打算把平时重学C++的总结,抽时间上传到博客! 1.切勿乱用带符号类型和无符号类型 #include <iostream> using namespace st ...
- Vue的项目打包为移动端(安卓手机应用)app
现在基于vue全家桶技术体系,基本上可以开发各端的各种应用,pc端的应用,开发完成以后,直接运行打包命令 yarn build 即可打包,部署到服务器端上线即可.那么,今天我们来聊一聊,开发好的vue ...
- 一篇文章搞定什么是nodeJs它和NPM关系与应用
现在前端的入门门槛越来越高了,不再是单纯 html+css+js,各种前端框架 层出不穷,各种ui组件库层出不穷. 模块化,打包化,各种工具库层出不穷,前端变成大前端 ,甚至前端可以搞定整个项目,通过 ...
- STM32为何在诸多的单片机中脱颖而出?
1.前言 在STM32之前,都是老大头51,带着它的"小弟们" MSP430.AVR.PIC在单片机界呼风唤雨.那个时候,市场上遍布8位机,大学教材用51入门,个人.企业学单片机 ...
- Galaxy v-21.01 发布,新的流程和历史栏体验
Galaxy Project(https://galaxyproject.org/)是在云计算背景下诞生的一个生物信息学可视化分析开源项目. 该项目由美国国家科学基金会(NSF).美国国家人类基因组研 ...
- R 语言关于 SSL 证书异常处理笔记
一.关于 TCGAbiolinks TCGAbiolinks 是一个用于 TCGA 数据综合分析的 R/BioConductor 软件包,能够通过 GDC Application Programmin ...