一 前言
C语言并没有内链表置这种数据结构,所以Redis构建了自己的链表实现。
链表在Redis中的应用非常广泛,比如列表键的底层实现之一就是链表。当一个列表键包含了数量比较多的元素,又或者列表中包含的元素都是比较长的字符串时,Redis就会使用链表作为列表键的底层实现。
除了链表键之外,发布与订阅、慢查询、监视器等功能也用到了链表,Redis服务器本身还使用链表来保存多个客户端的状态信息,以及使用链表来构建客户端输出缓冲区(output buffer)。
二 列表键
2-1 特点
列表类型有两个特点:
- 列表中的元素是有序的,这就意味着可以通过索引下标获取某个元素或者某个范围内的元素列表。
- 列表中的元素可以是重复的
2-2 内部实现
在Redis3.2版本以前列表类型的内部编码有两种。
ziplist(压缩列表)
:当列表的元素个数小于list-max-ziplist-entries配置(默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使用。linkedlist(链表)
:当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。
而在Redis3.2版本开始对列表数据结构进行了改造,使用 quicklist 代替了 ziplist 和 linkedlist.
2-3 消息队列实现
列表类型可以实现先进先出的功能,同时轻松的弹出(查询并删除)第一个元素,所以列表类型可以用来实现消息队列
三 数据结构
/*
* 双端链表结构
*/
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;
/*
* 双端链表节点
*/
typedef struct listNode {
// 前置节点
struct listNode *prev;
// 后置节点
struct listNode *next;
// 节点的值
void *value;
} listNode;
/*
* 双端链表迭代器
*/
typedef struct listIter {
// 当前迭代到的节点
listNode *next;
// 迭代的方向
int direction;
} listIter;
四 数据结构图示
Redis链表使用双向无环链表,如图所示:
同时Redis为了方便的操作链表,提供了一个list结构来持有链表。如下图所示
五 特性
Redis链表结构其主要特性如下:
双向:链表节点带有前驱、后继指针获取某个节点的前驱、后继节点的时间复杂度为0(1)。
无环: 链表为非循环链表表头节点的前驱指针和表尾节点的后继指针都指向NULL,对链表的访问以NULL为终点。
带表头指针和表尾指针:通过list结构中的head和tail指针,获取表头和表尾节点的时间复杂度都为O(1)。
带链表长度计数器:通过list结构的len属性获取节点数量的时间复杂度为O(1)。
多态:链表节点使用void*指针保存节点的值,并且可以通过list结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用来保存各种不同类型的值。
六 问答
Q1:为什么redis链表节点的value字段是void *类型?
Q2:list结构里的函数指针是怎么回事?
Q3:什么是多态?
链接:https://www.jianshu.com/p/68ddb5484ca2
官方说:
- 接口的多种不同的实现方式即为多态。
- 多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术。
-
我们在程序中定义的引用变量所指向的具体类型和通过该引用变量的方法调用在编程的时候并不确定,当处于运行期间才确定。就是这个引用变量究竟指向哪一个实例对象,在编译期间是不确定的,只有运行期才能确定,这样不用修改源码就可以把变量绑定到不同的类实例上,让程序拥有了多个运行状态,这就是多态。
说人话:
允许将子类类型的指针赋值给父类类型的指针,把不同的子类对象都当作父类来看。比如你家有亲属结婚了,让你们家派个人来参加婚礼,邀请函写的是让你爸来,但是实际上你去了,或者你妹妹去了,这都是可以的,因为你们代表的是你爸,但是在你们去之前他们也不知道谁会去,只知道是你们家的人。可能是你爸爸,可能是你们家的其他人代表你爸参加。这就是多态。
多态又分为 编译时多态和运行时多态。
编译时多态:比如重载
运行时多态:比如重写