但要值得注意的是:Redis并没有直接使用这些数据结构来实现key-value数据库,而是基于这些数据结构创建了一个对象系统。
简单来说:Redis使用对象来表示数据库中的键和值。每次我们在Redis数据库中新创建一个键值对时,至少会创建出两个对象。一个是键对象,一个是值对象。
Redis中的每个对象都由一个redisObject结构来表示:
typedef struct redisObject{ // 对象的类型 unsigned type 4:; // 对象的编码格式 unsigned encoding:4; // 指向底层实现数据结构的指针 void * ptr; //..... }robj;简单来说就是Redis对key-value封装成对象,key是一个对象,value也是一个对象。每个对象都有type(类型)、encoding(编码)、ptr(指向底层数据结构的指针)来表示。
下面我就来说一下我们Redis常见的数据类型:string、list、hash、set、sortset。它们的底层数据结构究竟是怎么样的!
2.1SDS简单动态字符串简单动态字符串(Simple dynamic string,SDS)
Redis中的字符串跟C语言中的字符串,是有点差距的。
Redis使用sdshdr结构来表示一个SDS值:
struct sdshdr{ // 字节数组,用于保存字符串 char buf[]; // 记录buf数组中已使用的字节数量,也是字符串的长度 int len; // 记录buf数组未使用的字节数量 int free; }例子:
2.1.1使用SDS的好处SDS与C的字符串表示比较
sdshdr数据结构中用len属性记录了字符串的长度。那么获取字符串的长度时,时间复杂度只需要O(1)。
SDS不会发生溢出的问题,如果修改SDS时,空间不足。先会扩展空间,再进行修改!(内部实现了动态扩展机制)。
SDS可以减少内存分配的次数(空间预分配机制)。在扩展空间时,除了分配修改时所必要的空间,还会分配额外的空闲空间(free 属性)。
SDS是二进制安全的,所有SDS API都会以处理二进制的方式来处理SDS存放在buf数组里的数据。
2.2链表对于链表而言,我们不会陌生的了。在大学期间肯定开过数据结构与算法课程,链表肯定是讲过的了。在Java中Linkedxxx容器底层数据结构也是链表+[xxx]的。我们来看看Redis中的链表是怎么实现的:
使用listNode结构来表示每个节点:
typedef strcut listNode{ //前置节点 strcut listNode *pre; //后置节点 strcut listNode *pre; //节点的值 void *value; }listNode使用listNode是可以组成链表了,Redis中使用list结构来持有链表:
typedef struct list{ //表头结点 listNode *head; //表尾节点 listNode *tail; //链表长度 unsigned long len; //节点值复制函数 void *(*dup) (viod *ptr); //节点值释放函数 void (*free) (viod *ptr); //节点值对比函数 int (*match) (void *ptr,void *key); }list具体的结构如图:
2.2.1Redis链表的特性Redis的链表有以下特性:
无环双向链表
获取表头指针,表尾指针,链表节点长度的时间复杂度均为O(1)
链表使用void *指针来保存节点值,可以保存各种不同类型的值
2.3哈希表声明:《Redis设计与实现》里边有“字典”这么一个概念,我个人认为还是直接叫哈希表比较通俗易懂。从代码上看:“字典”也是在哈希表基础上再抽象了一层而已。
在Redis中,key-value的数据结构底层就是哈希表来实现的。对于哈希表来说,我们也并不陌生。在Java中,哈希表实际上就是数组+链表的形式来构建的。下面我们来看看Redis的哈希表是怎么构建的吧。
在Redis里边,哈希表使用dictht结构来定义:
typedef struct dictht{ //哈希表数组 dictEntry **table; //哈希表大小 unsigned long size; //哈希表大小掩码,用于计算索引值 //总是等于size-1 unsigned long sizemark; //哈希表已有节点数量 unsigned long used; }dictht