Redis源码解析:02链表
链表提供了高效的节点重排能力,以及顺序性的节点访问方式,因为Redis使用的C语言并没有内置这种数据结构,所以Redis自己实现了链表。
链表在Redis中的应用非常广泛,比如列表的底层实现之一就是链表。当一个列表中包含的元素比较多时,又或者列表中包含的元素都是比较长的字符串时,Redis就会使用链表作为列表的底层实现。
除了列表之外,Redis中的发布与订阅、慢查询、监视器等功能也用到了链表,Redis服务器本身还使用链表来保存多个客户端的状态信息,以及使用链表来构建客户端输出缓冲区。
在adlist.h中,链表节点的定义如下:
typedef struct listNode
{
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
多个listNode可以通过prev和next指针组成双向链表,如下图所示:
在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;
list结构为链表提供了头指针head、尾指针tail,以及链表长度计数器len,而函数指针dup,free和match则是用于实现多态链表所需的类型特定函数:
dup函数用于复制链表节点的值;
free函数用于释放链表节点的值;
match函数则用于对比链表节点所保存的值和另一个输入值是否相等。
下图是由一个list结构和三个listNode结构组成的链表:
Redis的链表实现的特性可以总结如下:
双向:链表节点带有prev和next指针,获取某个节点的前置节点和后置节点的时间复杂度都是O(1);
无环:表头节点的prey指针和表尾节点的next指针都指向NULL,对链表的访问以NULL为终点;
多态:链表节点使用void*指针来保存节点值,并且可以通过list结构的dup、free和match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。
adlist.c是链表的实现源码文件,其中的代码都比较简单。比较有意思的是它内部实现了一个链表迭代器listIter,它的结构体定义如下:
typedef struct listIter {
listNode *next;
int direction;
} listIter;
其中,next表示使用迭代器当前指向的链表节点,对迭代器调用next操作,就返回该指针,并将next指向下一个节点。direction就表示迭代器的迭代方向,如果direction为AL_START_HEAD,表示迭代器从head开始从左到右迭代;如果direction为AL_START_TAIL,则表示迭代器从tail开始从右到左迭代。
迭代器的主要代码如下:
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;
} void listReleaseIterator(listIter *iter)
{
zfree(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;
} 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:
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;
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;
}
其他关于redis的list代码,可以参考:
https://github.com/gqtc/redis-3.0.5/blob/master/redis-3.0.5/src/adlist.c
Redis源码解析:02链表的更多相关文章
- Redis源码解析之跳跃表(三)
我们再来学习如何从跳跃表中查询数据,跳跃表本质上是一个链表,但它允许我们像数组一样定位某个索引区间内的节点,并且与数组不同的是,跳跃表允许我们将头节点L0层的前驱节点(即跳跃表分值最小的节点)zsl- ...
- .Net Core缓存组件(Redis)源码解析
上一篇文章已经介绍了MemoryCache,MemoryCache存储的数据类型是Object,也说了Redis支持五中数据类型的存储,但是微软的Redis缓存组件只实现了Hash类型的存储.在分析源 ...
- Redis源码解析:15Resis主从复制之从节点流程
Redis的主从复制功能,可以实现Redis实例的高可用,避免单个Redis 服务器的单点故障,并且可以实现负载均衡. 一:主从复制过程 Redis的复制功能分为同步(sync)和命令传播(comma ...
- Redis源码解析:13Redis中的事件驱动机制
Redis中,处理网络IO时,采用的是事件驱动机制.但它没有使用libevent或者libev这样的库,而是自己实现了一个非常简单明了的事件驱动库ae_event,主要代码仅仅400行左右. 没有选择 ...
- Spring源码解析02:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析
一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...
- Redis源码解析
一.src/server.c 中的redisCommandTable列出的所有redis支持的命令,其中字符串命令包括从get到mget:列表命令从rpush到rpoplpush:集合命令包括从sad ...
- Redis源码解析:26集群(二)键的分配与迁移
Redis集群通过分片的方式来保存数据库中的键值对:一个集群中,每个键都通过哈希函数映射到一个槽位,整个集群共分16384个槽位,集群中每个主节点负责其中的一部分槽位. 当数据库中的16384个槽位都 ...
- Redis源码解析:25集群(一)握手、心跳消息以及下线检测
Redis集群是Redis提供的分布式数据库方案,通过分片来进行数据共享,并提供复制和故障转移功能. 一:初始化 1:数据结构 在源码中,通过server.cluster记录整个集群当前的状态,比如集 ...
- Redis源码解析之跳跃表(一)
跳跃表(skiplist) 有序集合(sorted set)是Redis中较为重要的一种数据结构,从名字上来看,我们可以知道它相比一般的集合多了一个有序.Redis的有序集合会要求我们给定一个分值(s ...
随机推荐
- spring四种依赖注入方式(转)
spring四种依赖注入方式!! 平常的java开发中,程序员在某个类中需要依赖其它类的方法,则通常是new一个依赖类再调用类实例的方法,这种开发存在的问题是new的类实例不好统一管理,spring提 ...
- Java问题解读系列之String相关---String、StringBuffer、StringBuilder的区别
今天的题目是String.StringBuffer和StringBuilder的区别: 首先还是去官方的API看看对这三种类型的介绍吧,Go...... 一.继承类和实现接口情况 1.String类 ...
- JavaScript类型转换规则
注:JavaScript相对于其他强类型(如c,c++,java,定义变量的同时申明变量类型)语言来说,是弱类型和松散型(在定义的时候不需要申明变量类型,在使用的时候才赋值给她什么类型就是什么类型.所 ...
- Liferay 7:portlet name
总结自: https://web.liferay.com/zh/web/user.26526/blog/-/blogs/proper-portlet-name-for-your-portlet-com ...
- 阿里云DMS发布数据库网关服务: 打通网络限制 开启数据库统一管理的万能钥匙
概述 阿里云数据管理DMS在云端可提供专业的数据库服务,除对标本地数据库软件的基础功能外,还包含性能诊断.数据追踪.跨实例SQL查询(含异构数据库类型之间)等专业性功能,同时提供审计安全和企业级数据库 ...
- HR招聘_(四)_招聘方法论(简历筛选)
.主动投递 筛选时需要看学历背景,公司平台,所在时间,岗位职责,项目经验几个部分,根据对岗位需求和企业文化选择合适的候选人. 筛选简历建议分岗位筛选,切记误操作或大意淘汰优秀候选人. .搜索下载 互联 ...
- 微信小程序-- WXML语法
页面数据 test.js test.wxml 运行结果:
- SVN 打包时,出现File not found: transaction '148-48', path ''https://xxxxxx/svn/xxxxx/tag/2017-9-30'
svn打包时,出现错误如下: 解决办法: 在SVN目录结构中,增加tag这个目录.
- jquery购物评分
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- 使用DIV+CSS布局网站的优点和缺陷
随着WEB2.0标准化设计理念的普及,国内很多大型门户网站已经纷纷采用DIV+CSS制作方法,从实际应用情况来看,此种方法绝对好于表格制作页面的方法. 如今大部分网站仍然采用表格嵌套内容的方式来制作网 ...