目录
一、动态字符串(SDS)
1.用途
1.存储字符串对象,例如键值对的键以及字符串值
2.SDS被用作缓冲区(buffer):AOF缓冲区和客户端的输入缓冲区
2.结构
struct sdshdr{
// 记录buf数组已使用字节的数量,等于所保存字符串的长度
int len;
// 记录buf数组未使用的字节数量
int free;
// 字节数组
char buf[]; //末尾'\0'不记录len中
}
3.SDS与C字符串的区别
1.获取字符串长度,时间复杂度为O(1)
2.杜绝缓冲区溢出,对SDS进行修改前,会先检查空间是否满足,不满足会进行扩容
3.减少修改字符串带来的内存重新分配次数(溢出和泄露)
1-空间预分配
修改之后SDS长度len小于1MB,则分配和len同样大小的未使用空间
修改之后SDS长度len大于1MB,则分配1MB的未使用空间
2-惰性空间释放
4.二进制安全,可以存储空字符
5.兼容部分C字符串函数
二、链表
1.用途
1.存储链表键,发布订阅、慢查询、监视器等
2.Redis服务器用链表保存多个客户端状态信息,构建客户端输出缓冲区
2.结构
节点
struct listNode{
// 前置节点
struct listNode *prev;
// 后置节点
struct listNode *next;
// 节点的值
void *value;
}
链表
// 双端 无环 头尾指针
// 头节点的前置节点和表尾节点的后置节点指向NULL
struct list{
// 表头节点
listNode *head;
// 表尾节点
listNode *tail;
// 链表所包含的节点数量
unsigned long len;
// 节点值复制函数
void *(*dup)(void *ptr)
// 节点值释放函数
void (*free)(void *ptr)
// 节点值对比函数
void (*match)(void *ptr, void *key)
}
三、字典
1.用途
实现哈希键、数据库
2.结构
哈希表
struct dictht{
// 哈希表数组
dictEntry **table;
// 哈希表大小
unsigned long size;
// 哈希表大小掩码,用于计算索引值
// 总是等于size-1
unsigned long sizemask;
// 该哈希表已有节点的数量
unsigned long used;
}
哈希表节点
struct dictEntry{
//键
void *key;
//值
union{
void val;
uint64_t u64;
int64_t s64;
} v;
// 指向下个哈希表节点,形成链表
struct dictEntry *next;
}
字典
struct dict{
// 类型特定函数
dictType *type;
// 私有数据
void *privdata;
// 哈希表
dictht ht[2];
// rehash索引
// 当rehash没在进行时,值为-1
int trehashidx;
}
privdata属性保存了需要传给特定函数的可选参数
dictType保存了特定类型键值对的函数
struct dictType{
// 各种函数...
}
3.解决键冲突
使用链地址法,将新节点添加到链表的表头位置,时间复杂度为O(1)
4.rehash
为了维持负载因子在合理的范围之内,需要对哈希表进行扩展和收缩。
1.为字典的ht[1]哈希表分配空间
扩展:ht[1]>=ht[0].used*2的2的n次方幂
收缩:ht[1]>=ht[0].used的2的n次方幂
2.将保存在ht[0]中的所有键值对rehash到ht[1]上面
3.rehash完成后,释放ht[0],将ht[1]设置为ht[0],并在ht[1]新创建一个空白哈希表
5.渐进式rehash
1.ht[1]分配空间
2.在字典中维持一个索引计数器rehashidx,置为0,表示rehash正式开始
3.每次对字典进行添加、查找等操作的时候,除了执行指定的操作以外,还会讲ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1],完成后,rehashidx值加1
4.所有键值rehash成功后,rehashidx值为-1
rehash过程中,查找操作先在ht[0]中查询,如果不存在在到ht[1]
新添加的键值保存到ht[1]
四、跳跃表
1.用途
有序集合包含的元素数量多或者元素成员是比较长的字符串,实现有序集合键
2.结构
跳跃表节点
struct zskiplistNode{
// 后退指针
struct zskiplistNode *backward;
// 分值 浮点数
double score;
// 成员对象 字符串对象SDS 唯一
robj *obj;
// 层 每个元素包含一个指向其他节点的指针,加快访问速度
struct zskiplistLevel{
// 前进指针
struct zskiplistNode *forward;
// 跨度
unsigned int span; // 记录两个节点直接的距离
} level[];
}
跳跃表
struct zskiplist{
// 表头、表尾节点
struct zskiplistNode *header,*tail;
// 表中节点的数量
unsigned long length;
// 表中层数对打节点的层数
int level;
}
五、整数集合(intset)
1.用途
当一个集合只包含整数值元素且不多时,实现集合键
2.结构
struct intset{
// 编码方式
uint32_t encoding;
// 集合包含的元素数量
uint32_t length;
// 保存元素的数组
int8_t contents[];
}
contents数组按照从小到大的顺序保存着集合中的元素
3.升级
当新元素加到整数集合里面且新元素比现有集合的元素类型都要大的时候,需要进行升级才能够将新元素添加到集合。
1.根据新元素类型,扩展整数集合底层数组的空间大小,为新元素分配空间
2.将集合现有元素都转换成与新元素相同的类型,并按照大小放置在正确的位置,且保持有序
3.添加新元素(头或者尾)
4.升级的好处
1.提升灵活性
2.节约内存
3.不支持降级
五、压缩列表
1.用途
当列表键包含的元素较少时并且是小整数值或者短字符串;当哈希键包含少量键值对并且是小整数值或者短字符串;实现列表键和哈希键
2.结构
压缩列表
struct ziplist{
// 占用字节数
uint32_t zlbytes;
// 表尾节点距离起始地址的字节数
uint32_t zltail;
// 节点数量
uint16_t zllen;
// 列表节点 多个
ziplistNode entryX;
// 末端标记位
uint8_t zlend;
}
列表节点
struct ziplistNode{
// 前一个节点长度 1字节或者5字节
// 前一字节<254 则为1字节 >=254 则为5字节
x previous_entry_length;
// 保存数据类型及长度
y encoding;
// 保存节点的值
encoding content;
}
3.连锁更新
由于previous_entry_length的原因,当列表插入新节点或者删除节点时,有可能会发生后一节点的previous_entry_length需要修改的情况,比如从1字节修改为5字节,这要可能导致后续节点都进行修改,所以连锁更新最坏的复杂度为O(n^2)。