简单的动态字符串
- redis没有直接使用C语言传统的字符串表示,而自己构建了一个动态字符串SDS,当redis需要的不仅仅是一个字符串字面量,而是一个可以被秀噶ide字符串值时,redis就会使用sds来表示字符串值,比如在redis的数据库里,包含字符串值的键值对在底层都是由SDS实现的。
redis > set name "bugall"
ok
1.键值对的键是一个字符串对象,对象的底层实现是一个保存着字符串"name"的SDS
2.键值对的值也是一个字符串对象,对象的底层实现是一个保存这字符串"bugall"的SDS
- 除了用来保存数据库中的字符串值之外,SDS还被用作缓冲区,AOF模块中的AOF缓冲区,以及客户端状态中的输入缓冲区。
struct sdshdr{
//记录buf数组中已使用字节的数量
//等于SDS所保存字符串的长度
int len;
//记录buf数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
}
1. free属性的值为0,表示这个SDS没有分配任何未使用空间。
2. len属性的值为5,表示这个SDS保存了一个五字节长的字符串
3. buf属性是一个char类型数组,数组的前5个字节分别保存了‘r','e','d','i','s'五个字符,而最后一个字节则保存了空字符'\0'
注意:保存孔子福的1字节空间不计算在SDS的len属性里面,并且为空字符分配额外的一字节空间。
与c字符串不同,SDS的空间分配策略完全杜绝了发生缓冲区溢出的可能性,当SDS api 需要对SDS进行修改时,API 会先检查SDS的空间是否满足修改所需要的要求,如果不满足的话,API会自动将SDS的空间拓展至执行修改所需要的大小,然后才执行实际的修改操作,所以使用SDS即不需要手动修改SDS的空间大小,也不会出现的缓冲区溢出的情况。
空间分配策略:
为了避免C字符串的这种缺陷,SDS通过未使用空间解除了字符串长度和底层数组长度之间的关联,在SDS中,buf数组的长度不一定就是字符数量加一,数组里面可以包含未使用的字节,而这些字节的数量就由SDS的fress属性记录。通过未使用空间,SDS实现了空间的预分配和惰性空间释放两种优化策略空间预分配:
空间预分配用于优化SDS的字符串增长操作:当SDS的API对一个SDS进行修改,而且需要对SDS进行空间拓展的时候,程序不仅会为SDS分配修改所必须要的空间,还会为SDS分配额外的未使用空间。通过空间的预分配策略,redis可以减少连续执行字符串增长操作所需的内存重分配次数。惰性空间释放
惰性空间释放用于优化SDS字符串缩短操作:当SDS的API需要缩短字符时,程序并不是立即使用内存重分配来回收缩短后多出来的字节,而是将这些字节的数量记录起来(free),并等待将来使用SDS还是二进制安全的,支持C字符串函数
链表
链表提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可以通过增删节点来灵活的调整链表的长度。因为Redis使用的C语言并没有内置这种数据结构,所以Redis构建了自己的链表实现。链表在Redis中的应用非常广泛,比如列表键的底层实现之一就是链表,当一个列表键包含了数量比较多的元素,又或者列表中包含的元素都是比较长的字符串时,Redis就会使用链表作为列表键的底层实现。
integers列表键的底层实现就是一个链表,链表中的每个节点都保存了一个整数值。除了链表键之外,发布与订阅,慢查询,监视器等功能也用到了链表,Redis服务器本身还是用链表来保存多个客户端的状态信息,以及使用链表来构建客户端的输出缓冲区。
typeof struct list {
//表头借点
listNode *head
//表尾节点
listNode *tail
//链表所包含的节点数量
unsigned long len
//节点值复制函数
void *(*dup)(void *ptr);
//节点值释放函数
void (*free)(void *ptr);
//节点值对比函数
void (*match)(void *ptr,void *key)
} list
字典
- 字典,又称为符号表,关联数组或映射,是一种用于保存键值对的抽象数据结构,Redis的数据库就是用使用字典作为底层实现的。除了用来表示数据库之外,字典还是哈希键的底层实现之一,当一个哈希键包含的键值对比较多,又或者键值对中的元素都是比较长的字符串时。Redis就会使用字典作为哈希键的底层实现。举个例子:
a={}
a{
users:{name:'bugall',name:'yifang'}
}
其中'users'可以理解为上稳重的哈希键,键值对就是'users'对应的内容。
- Redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对。
typedef struct dictht{
//哈希表数组
dictEntry **table;
//哈希表大小
unsigned long size;
//哈希表大小掩码,用于计算索引值
//总是等于size-1
unsigned long sizemaske;
//该哈希表已有节点的数量
unsigned long used;
} dictht;
哈希表节点:
typedef struct dictEntry{
//键
void *key
//值
union{
void *val;
uint64_tu64;
int64_ts64;
} v;
struct dictEntry *next;
} dictEntry;
next属性是指向另一个哈希表节点的指针,这个指针可以将多个哈希值相同的键值对连接一次,以此来解决冲突的问题
字典:
typedef struct dict {
//类型特定函数
dictType *type;
//私有数据
void *privdata;
//哈希表
dictht ht[2];
//rehash索引
in trehashidx;
} dict;
整数集合
- 整数集合是集合键的底层实现之一,当一个集合只包含整数数值元素,并且这个集合的元素数量不多时,Redis就会使用整数集合作为集合键的底层实现。举个例子,如果我们创建一个只包含5个元素的集合键,并且集合中的所有元素都是整数值,那么这个集合键的底层实现就会是整数集合
typedef struct intset{
//编码方式
uint32_t encoding;
//集合包含的元素数量
uint32_t length;
//保存元素的数组
int8_t contents[];
} intset;
contents数组是整数集合的底层实现:整数集合的每个元素都是contents数组中的任意一个item(元素),各个项在数组中按值的大小从小到大有序的排列
length属性记录了整数集合包含的元素数量,也是contents数组的长度。
虽然intset结构将contents属性声明为int8_t类型的数则,但实际上contents数组并不保存任何int8_t类型的值,contents数组的真正类型取决于encoding属性的值。这也是整数集合的特点之一,动态的更改contents的大小
- 升级
每当我们要将一个新元素添加到整数集合里面,并且新元素的类型比整数集合现有所有元素 的类型要长时,整数集合需要先进行升级,然后才能将新元素添加到整数集合里面
1.根据新元素的类型,扩展整数集合底层数组的空间大小,并为新元素分配空间。
2.将底层数组现有的所有元素都转换成与新元素相同的类型,并将类型转换后的元素放置到正确的位置上,而且在放置元素的过程中,需要继续维持底层数组的有序性质不变。
3.将新元素放到底层数组里面
注意整数集合不支持降级操作
压缩列表
压缩列表是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。
另外,当一个哈希键只包含少量键值对,并且每个键值对的键和值要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做哈希键的底层实现。
压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值
每个压缩列节点可以保存一个字节数组或是一个整数值,每个压缩列表节点都由previous_entry_length,econding,content三个部分组成
1.previous_entry_length记录了压缩列表中前一个节点的长度。因为节点的previous_entry_length属性记录了前一个节点的长度,所以程序可以通过指针运算,根据当前节点的起始地址来计算出前一个节点的起始地址。
2.encoding属性记录了节点的content属性所保存数据的类型以及长度
3.content属性负责保存节点的值,节点值可以是一个字节数组或者整数,值的类型和长度由节点的encoding属性决定
属性 | 类型 | 字节长度 | 用途 |
---|---|---|---|
zlbytes | uint32_t | 4 | 记录整个压缩列表占用的内存字节数:在对压缩列表进行内存重分配,或者计算zlend的位置时使用 |
zltail | uint32_t | 4 | 记录压缩列表尾节点距离压缩列表的起始地址有多少字节:通过这个偏移量,程序无需遍历真个压缩列表就可以确定表尾节点的地址 |
zllen | uint16_t | 2 | 记录了压缩列表包含的节点数量:当这个属性的值小雨uint16_max时,这个属性的值就是压缩列表包含节点的数量;当这个值等于uint16_max时,节点的真实数量需要遍历整个压缩列表才能计算得出 |
entryX | 列表节点 | 不定 | 压缩列表包含的各个节点,节点的长度由节点保存的内容决定 |
zlend | uint8_t | 1 | 特殊值0xFF(十进制255),用于标记压缩列表的末端 |