其他文章:
Redis源码解析(1) 动态字符串与链表
Redis源码解析(2) 字典与迭代器
Redis源码解析(3) 跳跃表
Redis源码解析(4) 整数集合
Redis源码解析(5) 压缩列表
Redis源码解析(6) 键的过期处理策略
Redis源码解析(7) 发布订阅机制
Redis源码解析(8) AOF持久化
Redis源码解析(9) RDB持久化
Redis源码解析(10) 网络框架
Redis源码解析(11) 内存淘汰策略
Redis源码解析(12) 命令执行过程
Redis源码解析(13) 主从复制
Redis源码解析(14) 哨兵机制[1] 结构与初始化
Redis源码解析(15) 哨兵机制[2] 信息同步与TILT模式
Redis源码解析(16) 哨兵机制[3] 判断下线
Redis源码解析(17) 哨兵机制[4] 故障转移
Redis源码解析(18) 集群[1]初始化,握手与心跳检测
Redis源码解析(19) 集群[2] 主从复制,故障检测与故障转移
Redis源码解析(20) 集群[3] 键的存储,重新分片与重定向
Redis源码解析(21) 集群[4] 故障转移failover与slave迁移
Redis源码解析(22) 事务
Redis源码解析(23) SCAN命令实现
引言
这篇文章只简单介绍两个数据结构的基础实现,函数部分实在是没有什么可说的,对于这两种结构熟悉的朋友可直接观看后述章节.
动态字符串
C语言中没有带有动态字符串的标准库,但是这种数据结构在很多情况下还是比较常用的,所以Redis中实现了简单的动态字符串.这个可以说是整个Redis的构建中最为常用的一种数据结构了,不但可以用作字典的键,还可以用作字典的值,甚至可以用作缓冲区.
基本结构如下:
struct sdshdr {
// buf 中已占用空间的长度 使用len来判断可以保证字节数组存储的对象二进制安全 即不以'\0'来判断结尾
int len;
// buf 中剩余可用空间的长度
int free;
// 数据空间
char buf[];
};
这其中我们可以看到一个很重要的点,就是动态字符串并不像一般的C字符串一样使用’\0’结尾来记录结尾(虽然结尾还是’’\0),而是使用结构体中加上一个成员来判断长度.这样不但在进行长度判断的时候可以O(1)完成,而且保证动态字符串是二进制安全的.
动态字符串相比于普通C系字符串有以下好处:
- 二进制安全(可存储汉字,音频,图片等).
- 不会有使用C系标准库的字符串函数时出现的缓冲区溢出问题.
- 兼容部分标准库的字符串函数.
说实话,在动态字符串中其实没什么好说的,所有的API都非常简单,在sds.h/sds.c中.有兴趣的朋友可以挑几个函数看看实现.
链表
链表也是一个在Redis中使用十分广泛的一个数据结构,它可以做的事情实在是太多了:
- 五大存储类型之一.
- 存储发布订阅中客户端信息.
- 集群中故障转移中存储判断某个节点已下线的信息.
- 回复缓冲区.
- 事务中服务器存储命令.
- …
所以这是一个非常重要的数据结构,但它同时也是比较简单的.
listNode
是每个节点的数据,带着两个指针的同时有一个void
指针,指向真实存储的值,那么如何知道其信息呢,一般在使用链表的时候我们很清楚这个链表的作用是什么,也不会在其中存储两个类型的数据,所以在使用的时候一个强制类型转换就可以了.
typedef struct listNode {
// 前置节点
struct listNode *prev;
// 后置节点
struct listNode *next;
// 节点的值
void *value; //没有泛型 这也是没办法的办法
} listNode;
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;
一个链表的结构基本就是这样:
我们可以看到在就够提中还有几个函数指针,它们的功能分别是复制,释放和比较,因为值的默认存储是void,与其在后面使用时进行强制类型转换倒不如初始化的时候先注册好,减少使用的困难程度.
当然有这种结构的话迭代器也是必不可少的,Redis链表中也自带迭代器,但它比起SCAN中的游标可要简单多了,我们来看看其实现吧:
typedef struct listIter {
// 当前迭代到的节点
listNode *next;
// 迭代的方向
int direction;
} listIter;
通过记录迭代方向,我们可以很方便的进行两个方向的迭代.
函数同样不想说太多,都是C语言基础,这里就不费口舌了,大家把重点放在后面系列的文章,那些才是阐明Redis的重点所在,这篇文章就是为了能使整个基础结构部分能够有头有尾而存在的.