一.简单动态字符串(SDS)
1.定义
struct sdshdr {
int len;记录已使用字节数量
int free;记录未使用字节数量
char buf[];字节数组对象
}
2.此设计的优缺点
优点:
优化查询的时间复杂度,与c的String相比较,多了len和free两个字段,获得已使用长度、未使用长度和总长度的时间复杂度都是O(1);
通过对free的监控,还可以有效的杜绝内存的溢出,因为在给字符串追加的时候,SDS会自动检测剩余的空间是否足够如果不够,会自动扩充;
减少内存重新分配的次数,策略一,使用空间预分配技术,当字符串的长度小于1M时,会分配给free同等长度的空间,当字符串的长度大于1M时,会分配给free1M的空间,策略二:惰性空间释放,当字符串长度缩减的时候,这部分内存不会立即回收,而是将这部分空间存到free中待用,当然SDS也提供了API,来释放真正未使用的空间(这个还涉及到了内存释放的策略),减少内存的浪费;
二进制安全,SDS会以二进制的方式来保存数据,处理在buff数组里的数据,不会对其中的数据进行限制、过滤等,对比c字符串,当c字符串保存的数据存在空格的时候,c字符串的函数识别字符串的时候是以'\0'来识别字符串的,故会出现将字符串空格后面的数据落下的错误识别,而以SDS以len来作为判断字符串长度的标准就不会出现这种问题。
缺点:
浪费了一定的内存空间。
3.设计中的好idea
SDS采用了与c相同的字符串结尾加个'\0'的思想,来标识这个是字符串('\0’不记录到len中)。
好处是SDS可以延用一部风c的字符串操作函数,不必再次开发。如printf("%s", XXX);字符串打印函数。
二.链表(双端)
1.定义
typedef struct listNode {
Struct listNode* prev;
Struct listNode* next;
void* value;
}listNode;
typedef struct list {
listNode* head;
listNode* tail;
unsigned int len;
void *(*dup) (void *ptr); //节点复制函数
void (*free) (void *ptr); //节点释放函数
void (*match) (void *ptr, void *key); //节点对比函数
}
2.优缺点
优点
易删除,插入;
记录了链表的节点数量,获得链表长度的时间复杂度是O(1);
无环,头结点的pre指向null,尾节点的next指向null。
缺点
查询慢
3.基本上的操作和c相似
三.字典
1.定义
用于保存键值对的抽象数据结构,使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对。
哈希表
typedef struct dictht {
dictEntry **table; //指向指针的指针,table是一个数组,数组的每个元素是指向dictEntry的指针
unsigned long size; //哈希表大小,即table数组的大小
unsigned long sizemask; //哈希表大小的掩码用于计算索引,总是等于size-1,这个属性和hash值共同决定一个键值对应该被放到table数组的哪一个索引上
unsigned long used; //哈希表已有节点数量
}dictht;
哈希节点
typedef struct dictEntry {
void *key; //键
union{
void *val;
unit64_t u64;
int64_t s64;
} v; //值
struct dictEntry *next; //指向下一个节点形成链表
}
unit64_t和int64_t区别:无符号64位int型,和有符号64位int型。
*next;用来解决下一个指针冲突问题
字典
typedef struct dict {
dictType *type; //类型特定函数
void *privdate; //私有数据
dictht ht[2]; //哈希表
int trehashidx; //rehash 索引,当rehash不在进行时,值为-1
} dict;
*type 是一个指向dictType的指针,每个类型都有其特定类型的函数
*private 保存了需要传给那些特定类型函数的可选参数。
ht[2] 是指一张存放数据的表,一张空表预留着到rehash的时候有用。
哈希算法
根据键值对的键计算出其哈希值,然后通过哈希值和
sizemask来计算键值对放到table上的索引值。
冲突键解决
哈希节点有个next的指针通过这个属性可以将冲突的key挂在链表的最前面。
rehash的策略
当哈希表的负载因子到一个界限的时候就会执行rehash操作,具体不执行bigsave的情况下是1,执行的情况下是5,负载因子=used/size;负载因子小于0.1开始收缩
扩张策略,将存储空间扩大为,满足used*2<=2的n次方时,最小的2的n次方;将空间收缩,满足used<=2的n次方时,最小的2的n次方;
rehash的时候,是渐进式完成的不是一次性完成的。
渐进式rehash的时候,trehashidx初始值为0,每次对字典进行增、删、改、查操作的时候都会加1,到最后一个值也从原来的table转到新的table后,原来的table初始化,trehashidx变为-1.
四.整数集合
1.定义
整数集合是集合键的底层实现之一,当一个集合只包含整数,且元素数量不多时会采用此结构来实现。
有序、无重复的保存整数数据。
typedef struct inset {
uint32_t encoding; //编码方式
uint32_t length; //集合包含的元素数量
int8_t contents[]; //保存元素的数组
}
contents数组的内容由encoding编码方式来决定,而不是int8_t,且不包含重复的内容。
2.升级
当添加新元素时,此元素的大小超过现有编码下数据的范围的时候,整数集合就要开始升级,然后才能将此元素添加进去。
升级分三步
1.根据新加入的数据大小计算出将要采用的编码应该是什么,再计算出所有元素(新旧相加)乘以此编码获得的重新分配的空间大小。
2.将旧元素转换编码放在适合的位置上,注意根据有序性,新旧元素的顺序不能发生变化。
3.将新元素添加数组里面(当新元素比所有的元素都大时,放在数组最末尾,相反,则放开头)。
3.优点
提升灵活性,不必定死数组的编码进而定死数组所能存的数的范围。
节约内存空间,让一个数组能保存所有的数,你需要一开始就把编码设成最大,而升级可以在你必要的时候才将你的编码扩大。
4.降级
整数集合不支持降级,一旦升级,把那个导致升级的数删除,编码保持不变
由于采用数组来存集合元素,故查找数据快,但是插入、删除数据慢。
五.跳跃表
1.定义
一种有序的数据结构,通过在每个节点维持多个指向其他节点的指针,从而达到快速访问节点的目的;
只用在有序集合,和集群节点中用作内部数据结构。
跳跃表节点
typedef struct zskplistNode {
struct zskiplistNode *backwoard; //后退指针
double score;//分值
robj *obj//成员对象
struct zskiplistLevel {
struct zskiplistNode *forword; //前进指针
unsigned int span; //跨度
} level[]//层
}
2.实现方式
跳跃表节点level的数量是随机的,每次创建跳跃表节点的时候,根据幂次定律(越大的数出现的概率越小)随机生介于1到32之间的值作为level的大小。
跨度指的是该节点到下一个节点之间的距离,指向null的所有前进指针的跨度是0。根据跨度的值可以定位节点的排位。
每个节点只有一个后退指针,所以每个节点的后退指针都是指向前一个节点的。
分值是double类型的浮点数,跳跃表中的所有节点都是按照分值从小到大排列的。
节点的成员对象是一个指向字符串对象的指针,字符串对象保存着一个SDS的值。
跳跃表中,成员对象必须是唯一的,但是分值可以相同,分值相同的成员对象按照字典序的大小来进行排序越小的越靠近表头。
跳跃表
typedef struct zskiplist {
struct zskipkistNode *header, *tail; //表头和表尾指针
unsigned long length; //表中节点数量
int level; //表中层数最大的节点层数
}
length和level都是不计算header节点在内的。
有序集合的底层实现之一
六.压缩表
1.定义
哈希键的底层实现之一,当一个列表键只包含少量列表项,并且列表项要不是小整数类型,要不就是短字符串类型时,采用压缩表作为列表键的底层实现。
压缩表是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩表可以包含任意多的节点,内个节点的数据类型是整数值或者字符数组。
typedef struct ziplist {
uint32_t zlbytes; //压缩表的大小
uint32_t zltail; //尾节点到头结点的偏移量
uint16_t zllen; //压缩表包含的节点数量,当超过65535时就要通过遍历的方式来计算得到压缩表的节点数。
struct entry entry{
previous_entry_length; //存储前一个节点的大小
encoding; //记录节点的编码格式
content; //节点内容(字节数组、整数),根据编码格式可知对应节点内容格式
};
}ziplist
2.连锁更新
增加或者删除节点可能会出现连锁更新的情况。