简单的动态字符串
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服务器本身还是用链表来保存多个客户端的状态信息,以及使用链表来构建客户端的输出缓冲区。

<??http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:sql;"> 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),用于标记压缩列表的末端 |
?