文章导航-readme

前言

    Redis链表为双向无环链表!

    图解Redis之数据结构篇——简单动态字符串SDS提到Redis使用了简单动态字符串,链表,字典(散列表),跳跃表,整数集合,压缩列表这些数据结构来操作内存,并且简单介绍了Redis简单动态字符串。本篇文章我们继续来分析链表。

    链表是一种非常常见的数据结构,在Redis中使用非常广泛,列表对象的底层实现之一就是链表。其它如慢查询,发布订阅,监视器等功能也用到了链表。

系列文章

一、复习链表

1.1 数组与链表

    数组需要一块连续的内存来存储,这个特性有利也有弊。好处是其支持根据索引下标"随机访问"(时间复杂度为O(1)),但是其插入与删除操作为了保证在内存中的连续性将会变得非常低效(时间复杂度为O(N)),并且其一经声明就要占用整块连续内存空间,如果声明过大,系统可能内存不足,声明过小又可能导致不够用,而当数组的空间不足的时候需要对其进行扩容(申请一个更大的空间,将原数组拷贝过去)。

    而链表恰恰相反,其不需要一块连续的内存空间,其通过"指针"将一组零散的内存连接起来使用。其优点在于本身没有大小限制,天然支持扩容,插入删除操作高效(时间复杂度为O(1)),但缺点是随机访问低效(时间复杂度为O(N))。并且由于需要额外的空间存储指针。

    链表的实现方式有很多种,常见的主要有三个,单向链表、双向链表、循环链表。

1.2 单链表

    单链表中每个节点除了包含数据之外还包含一个指针,叫后继指针,因此需要额外的空间来存储后继节点的地址。有两个特殊的节点,头结点和尾节点,其中头节点用来记录链表的基地址,有了它就可以遍历整个链表,尾节点的后继指针不是指向下一个节点,而是指向一个空地址NULL表示这是链表上最后一个节点。与数组一样,单链表也支持数据的查找、插入和删除操作,其中插入和删除操作只需要考虑相邻节点指针的变化,因此为常数级时间复杂度O(1)。要想随机访问第 k 个元素,就没有数组那么高效了。因为链表中的数据并非连续存储的,所以无法像数组那样,根据首地址和下标,通过寻址公式就能直接计算出对应的内存地址,而是需要根据指针一个结点一个结点地依次遍历,直到找到相应的结点,因此时间复杂度为O(N)。

1.3 双向链表

    双向链表和单链表不同的是多了一个前驱指针,双向链表需要额外的两个空间来存储后继结点和前驱结点的地址。因此存储同样多的数据,双向链表占用比单链表更多的空间。但其优点在于支持双向遍历,体现在以下两个方面。

  • 在有序链表中查找某个元素,单链表由于只有后继指针,因此只能从前往后遍历查找时间复杂度为O(N),而双向链表可以双向遍历。
  • 删除给定指针指向的结点。假设已经找到要删除的节点,要删除就必须知道其前驱节点和后继节点,单链表想要知道其前驱节点只能从头开始遍历,时间复杂度为0(n),而双向链表由于保存了其前驱节点的地址,因此时间复杂度为0(1)。

1.4 循环链表

    顾名思义。循环链表与单、双链表不同的是其呈环状,单循环链表中其尾节点并非指向NULL而是指向头结点。双循环链表中其头节点的前驱指针指向尾节点,尾节点的后继指针指向头结点。循环链表的优势在于链尾到链头,链头到链尾比较方便适合处理的数据具有环型结构特点。

二、Redis链表

2.1 双向无环链表

    Redis链表使用双向无环链表。

如图所示,Redis使用一个listNode结构来表示。

typedef struct listNode
{
// 前置节点
struct listNode *prev;
// 后置节点
struct listNode *next;
// 节点的值
void *value;
} listNode;

2.2 list结构

    同时Redis为了方便的操作链表,提供了一个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;

Redis链表结构其主要特性如下:

  • 双向:链表节点带有前驱、后继指针获取某个节点的前驱、后继节点的时间复杂度为0(1)。
  • 无环: 链表为非循环链表表头节点的前驱指针和表尾节点的后继指针都指向NULL,对链表的访问以NULL为终点。
  • 带表头指针和表尾指针:通过list结构中的head和tail指针,获取表头和表尾节点的时间复杂度都为O(1)。
  • 带链表长度计数器:通过list结构的len属性获取节点数量的时间复杂度为O(1)。
  • 多态:链表节点使用void*指针保存节点的值,并且可以通过list结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用来保存各种不同类型的值。

2.3 双向无环链表在Redis中的使用

    链表在Redis中的应用非常广泛,列表对象的底层实现之一就是链表。此外如发布订阅、慢查询、监视器等功能也用到了链表。我们现在简单想一想Redis为什么要使用双向无环链表这种数据结构,而不是使用数组、单向链表等。既然列表对象的底层实现之一是链表,那么我们通过一个表格来分析列表对象的常用操作命令。如果分别使用数组、单链表和双向链表实现列表对象的时间复杂度对照如下:

操作\时间复杂度 数组 单链表 双向链表
rpush(从右边添加元素) O(1) O(1) O(1)
lpush(从左边添加元素) 0(N) O(1) O(1)
lpop (从右边删除元素) O(1) O(1) O(1)
rpop (从左边删除元素) O(N) O(1) O(1)
lindex(获取指定索引下标的元素) O(1) O(N) O(N)
len (获取长度) O(N) O(N) O(1)
linsert(向某个元素前或后插入元素) O(N) O(N) O(1)
lrem (删除指定元素) O(N) O(N) O(N)
lset (修改指定索引下标元素) O(N) O(N) O(N)

    我们可以看到在列表对象常用的操作中双向链表的优势所在。但双向链表因为使用两个额外的空间存储前驱和后继指针,因此在数据量较小的情况下会造成空间上的浪费(因为数据量小的时候速度上的差别不大,但空间上的差别很大)。这是一个时间换空间还是空间换时间的思想问题,Redis在列表对象中小数据量的时候使用压缩列表作为底层实现,而大数据量的时候才会使用双向无环链表。(关于列表对象后续会有文章继续介绍可访问我的个人博客持续关注www.kxamm.com)

小结

    链表作为一种非常常用的数据结构,内置在许多编程语言里面,更是找工作过程中经常问的面试题之一。本篇文章简单复习了链表这种数据结构常见的几种形式,并且简单分析了Redis中链表的使用。下篇文章将继续分享Redis中用到的数据结构Hash。敬请关注!

参考

《Redis设计与实现》

《Redis开发与运维》

《Redis官方文档》

图解Redis之数据结构篇——链表的更多相关文章

  1. 图解Redis之数据结构篇——简单动态字符串SDS

    图解Redis之数据结构篇--简单动态字符串SDS 前言     相信用过Redis的人都知道,Redis提供了一个逻辑上的对象系统构建了一个键值对数据库以供客户端用户使用.这个对象系统包括字符串对象 ...

  2. 图解Redis之数据结构篇——字典

    前言     字典在Redis中的应用非常广泛,数据库与哈希对象的底层实现就是字典. 系列文章 图解Redis之数据结构篇--简单动态字符串SDS 图解Redis之数据结构篇--链表 图解Redis之 ...

  3. 图解Redis之数据结构篇——跳跃表

    前言       跳跃表是一种有序的数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的.这么说,我们可能很难理解,我们可以先回忆一下链表. 一.复习跳跃表 1.1 什么 ...

  4. 图解Redis之数据结构篇——压缩列表

    前言     同整数集合一样压缩列表也不是基础数据结构,而是 Redis 自己设计的一种数据存储结构.它有点儿类似数组,通过一片连续的内存空间,来存储数据.不过,它跟数组不同的一点是,它允许存储的数据 ...

  5. 图解Redis之数据结构篇——整数集合

    前言     整数集合(intset)并不是一个基础的数据结构,而是Redis自己设计的一种存储结构,是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时, Redis i ...

  6. Redis设计与实现-内部数据结构篇

    题记:这本书是2015年11月份开始读的,大约花了一个多月的时间通读了一遍,最近由于需要对redis做一些深入的了解,因此又花了两个多月仔细精读了一遍,由于本书设计的内容较多,且每部分的内容都比较细致 ...

  7. 新秀nginx源代码分析数据结构篇(两) 双链表ngx_queue_t

    nginx源代码分析数据结构篇(两) 双链表ngx_queue_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csdn. ...

  8. 菜鸟nginx源代码剖析数据结构篇(八) 缓冲区链表ngx_chain_t

    菜鸟nginx源代码剖析数据结构篇(八) 缓冲区链表 ngx_chain_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog. ...

  9. 菜鸟nginx源码剖析数据结构篇(八) 缓冲区链表ngx_chain_t[转]

    菜鸟nginx源码剖析数据结构篇(八) 缓冲区链表 ngx_chain_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.c ...

随机推荐

  1. C#调用原生C++ COM对象(在C++中实现C#的接口)

    为了跨平台在.net core中使用COM,不能使用Windows下的COM注册机制,但是可以直接把IUnknown指针传给C#,转换为指针,再转换为C#的接口(interface). 做了这方面的研 ...

  2. Linux 中如何避免 rm -rf /*

    Linux 的删除命令中 rm中没有回收站的概念,一旦文件被删除比较难还原.更可怕的是rm -rf /*,连自己都能删 这命令太危险了,弄错了就核爆了,带来的损失的巨大的.比如 Gitlab.com ...

  3. Spark之UDF

    package big.data.analyse.udfudaf import org.apache.spark.sql.types.{IntegerType, StringType, StructF ...

  4. 新闻思考-阿里进军游戏产业,苹果发力ARM芯片

    2018.04.03 大家好,这是我开通博客的第一篇文章,我希望在这里分享我的知识,也学习更多的知识,希望大家学习愉快. 阿里进军游戏产业,拿下旅行青蛙的代理权.腾讯一直在进攻阿里的核心业务:电商和支 ...

  5. Symantec Backup Exec 2010 安装报 bad ELF interpreter: No such file or directory

    在64位的Red Hat Enterprise Linux Server release 6.6上安装Symantec Backup Exec 2010时, 遇到下面错误: # ./installra ...

  6. [20181220]使用提示OR_EXPAND优化.txt

    [20181220]使用提示OR_EXPAND优化.txt --//链接http://www.itpub.net/thread-2107240-2-1.html,http://www.itpub.ne ...

  7. 管理ESXI网络

    一.实验拓扑图: 二.实验目标:部署标准交换机和分布式交换机 三.实验步骤: 标准交换机的配置 1.分别在两台esxi主机中添加6块物理网卡,桥接到vmnet1. 通过宿主机连接到sql-vcente ...

  8. Linux 小知识翻译 - 「动态DNS」(DDNS)

    这次聊聊「动态DNS」. DNS上周已经介绍过了,就是提供主机名和IP地址对应关系的结构.「动态DNS」是对主机名和IP地址的对应关系提供动态管理的结构. 以前的DNS没有考虑IP地址变化的情况.但是 ...

  9. March 09th, 2018 Week 10th Friday

    All good things must come to an end. 好景无常. Love is when the other person's happiness is more importa ...

  10. 14.UA池和代理池

    今日概要 scrapy下载中间件 UA池 代理池 今日详情 一.下载中间件 先祭出框架图: 下载中间件(Downloader Middlewares) 位于scrapy引擎和下载器之间的一层组件. - ...