众所周知,redis有五种数据类型:字符串(string)、哈希(hash)、列表(list)、集合(set)、有序集合(sorted set),这五种数据类型底层又有不同的数据结构实现
redis字符串数据类型底层存储结构
struct sdshdr {
//记录buf数组中已使用字节的数量
//等于SDS所保存字符串的长度
int len;
//记录buf数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
};
为什么redis不采用C字符串而选择SDS的结构来存储字符串类型
(1)C字符串取长度需要遍历整个字符串,而SDS可以直接访问len属性获取字符串长度
(2)减少内存重分配次数,通过free属性,可预置未使用内存空间,当对字符串操作时,可以减少内存重分配次数
(3)二进制安全,C语言字符串以空字符结尾,不能存储类似以空字符分割的二进制数据串等,而SDS api则是以二进制的形式处理buf数组的数据,保证二进制安全
链表是列表数据类型的底层实现之一,当一个列表包含了数据比较多的元素,或者列表中包含的元素都是比较长的字符串时,列表就会采用链表来实现
typedef struct listNode {
// 前置节点
struct listNode * prev;
// 后置节点
struct listNode * next;
//节点的值
void * value;
}listNode;
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;
(1)redis链表为双向链表
(2)list结构保存了链表的头尾指针以及链表的节点数量,获取链表长度的时间复杂度为O(1)
(3)链表使用void来保存节点值,使得链表可以保存各种不同数据类型的值
字典是一种用于保存键值对的抽象数据结构,redis所使用的C语言没有内置类似java map的这种数据结构,因为其构建了自己的字典实现
redis的数据库就是基于字典来实现的,其增删改查操作就是建立在字典的基础上
字典也是哈希数据类型的底层实现之一,当一个哈希包含的键值对比较多,或者键值对中的元素都是比较长的字符串时,就会采用字典的数据结构
typedef struct dict {
//类型特定函数
dictType *type;
//私有数据
void *privdata;
//哈希表
dictht ht[2];
// rehash索引
//当rehash不在进行时,值为-1
in trehashidx; /* rehashing not in progress if rehashidx == -1 */
} dict;
(1)hash冲突:当发生hash冲突时,多个hash表节点用next指针形成一个单向链表
(2)再hash:随着键值对的数量越来越多,出现hash冲突的概率会增大,字典的读取效率会降低,因此需要进行扩容再hash
hash表的下一次容量大小会是 >= 当前已使用容量*2的第一个2的n次幂的值,例如当前hash表已使用3,那么下一次扩容容量大小为8
(3)负载因子计算:
hash表已保存的键值对的数量 / hash表的size
负载因子大于1或者小于0.1时进行扩容和缩容操作
跳表是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的
跳表是有序集合数据类型的底层实现之一,当有序集合的元素数量比较多,或者是有序集合元素的成员是比较长的字符串时,就会使用跳表作为底层实现
跳表在原来的有序链表的基础上,加上了多级索引,可以支持快速的插入、删除和查找操作
typedef struct zskiplistNode {
//层
struct zskiplistLevel {
//前进指针
struct zskiplistNode *forward;
//跨度
unsigned int span;
} level[];
//后退指针
struct zskiplistNode *backward;
//分值
double score;
//成员对象
robj *obj;
} zskiplistNode;
(1)跳表的level是随机生成的,介于1-32之间
(2)跳表以空间换时间,插入、查找和删除的时间复杂度均是O(logn)
(3)有序集合采用hash + 跳表结构实现,hash用于存储value到score的映射,可以在O(1)的时间内找到value对应的score
skiplist按从小到大的顺序存储分数,skiplist每个节点的值都是[value, score]对
整数集合是redis用于保存整数值的集合抽象数据结构,它可以保存int16,int32,int64的整数值
整数集合是集合数据类型的底层实现之一
typedef struct intset {
//整数集合中元素的编码方式
uint32_t encoding;
//整数集合的元素数量,即contents数组的长度
uint32_t length;
//保存元素的数组,数组中的元素从大到小有序排列,不会出现重复元素
//虽然contents数组被声明为int8_t类型,但数组中的元素的类型由encoding的值决定
//encoding的取值为int16_t、int32_t和int64_t中一种
int8_t contents[];
}intset;
压缩列表是列表和hash数据类型底层实现之一,当一个列表的元素个数很少且元素size也较小时,就会采用压缩列表存储;当一个hash键值对数量很少且键值size都较小时,就会采用压缩列表存储
(1)压缩列表是为了节约内存而开发的一种顺序型数据结构
(2)压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者整数值
(3)添加新节点到压缩列表,或者从压缩列表中删除节点,可能会引发连锁更新操作,但这种操作出现的几率并不高
五大数据类型底层采用的数据结构汇总:
(1)string字符串:采用SDS存储
(2)hash哈希: 当hash键值对数量很少且键值size都较小时,就会采用压缩列表存储;当hash类型无法满足ziplist条件时,采用字典存储
(3)list列表:当一个列表的元素个数很少且元素size也较小时,就会采用压缩列表存储;当列表不满足ziplist条件时,就采用链表存储
(4)set集合:当集合中的元素都是整数且元素个数小于512时,就采用整数集合存储;当不满足整数集合条件时,采用字典作为底层实现
(5)zset有序集合:当有序集合的元素个数小于128个,且元素的size小于64字节时,就采用压缩列表存储;当ziplist不满足条件时,就采用跳表存储