今天下午用一个小时复习了下Redis数据结构和对象,现在记录一下
如有表述不清或错误请斧正
简单动态字符串
在C字符串的基础上做了封装,底层是char类型的数组
SDS
struct sdshdr{
int len; // 字符串的长度
int free; // 空闲长度
char buf[]; // 字符数组
}
特点
-
O(1)获取字符串长度 -
杜绝缓冲区溢出的风险(
free属性记录空闲的可用空间数) -
减少内存分配和回收的次数
-
二进制安全(因为不以
\0判断结尾)
链表
其实就是双向无环链表
链表节点
typedef struct listNode{
listNode *prev;
listNode *next;
void *value;
}list;
链表
typedef struct list{
listNode *head;
listNode *tail;
unsigned long len;
void *(*dup)(void *ptr);
void *(*free)(void *ptr);
void *(*match)(void *ptr, void *key);
}list;
字典
字典 -> 哈希表 -> 哈希表节点
字典
typedef struct dict{
dictType *type; // dictType结构体含有一些函数指针,用于创建多态字典
void *privatedata; // 上述函数的参数
dictht ht[2]; // 平时只使用一个,rehash时使用另外一个
int rehashidx; // rehash索引
}dict;
哈希表
typedef struct dictht{
dictEntry **table; // 数组结构
unsigned long size; // 数组大小
unsigned long sizemask;
unsigned long used; // 哈希表含有的键值对数量
}dictht;
哈希表节点
typedef struct dictEntry{
void *key; // 键
union{
void *val; // 值
uint64_tu64;
int64_ts64;
}
dictEntry *next; // 拉链法处理哈希冲突,头插法
}dictEntry;
rehash
何时?
- 负载因子 = used / size
- 没有BGSAVE,且,没有BGREWRITEAOF,负载因子 > 1 时
- 在BGSAVE,或,在BGREWRITEAOF,负载因子 > 5 时
- 负载因子 < 0.1 时
- 扩容或收缩到2的n次方
渐进式
- rehash到
ht[1] - 使用
rehashidx记录rehash进度,开始时为0,结束时为-1 - 每次对字典的增删改查都会rehash当前
rehashidx上的所有记录,并rehashidx++ - 分配,rehash,指向,释放
- 新哈希表
ht[1]只增不减 - 所有操作会在原和新哈希表上发生
跳跃表
是顺序的数据结构,在链表上加索引来增加查找的效率,实现简单
跳跃表
typedef struct zskiplist {
struct zskiplistNode *header, *tail; // 首尾节点
unsigned long length; // 长度
int level; // 最大的level值
} zskiplist;
跳跃表节点
typedef struct zskiplistNode {
struct zskiplistLevel{
struct zskiplistNode *forward; // 前进指针
usigned int span; // 跨度
}level[]; // 层数组
struct zskiplistNode *backword; // 后退指针
double score; // 分值
sds ele; // 成员对象,指向一个字符串对象,SDS
} zskiplistNode;
注意
- score可以相同
- 成员对象必须要唯一
- score相同时按字典序排
- 跳表的相邻两层的节点数量最理想的比例是 2:1,查找复杂度可以降低到 O(logN)
- 新插入的节点的层高是随机的
压缩列表
顺序内存,适用于较短的字符串和较少的整数值
压缩列表
- zlbytes
- zltail
- zllen
- entry * N
- zlend
压缩列表节点
- previous_entry_length
- encoding
- content
连锁更新
可能连锁更新,不过对性能的影响可以忽略
整数集合
本质是数组,顺序内存
整数集合
typedef struct intset {
uint32_t encoding;
uint32_t length;
int8_t contents[];
} intset;
升级
- 只能升级,不能降级
- 新到的元素大于现有编码的数据类型所能容纳的范围时发生
- 新到的元素总是在升级后的第一个/最后一个
quicklist
可以理解为「双向链表 + 压缩列表」
一个quicklist是一个双向链表,链表的每一个节点时一个压缩列表
在向 quicklist 添加一个元素的时候,不会像普通的链表那样,直接新建一个链表节点。而是会检查插入位置的压缩列表是否能容纳该元素,如果能容纳就直接保存到 quicklistNode 结构里的压缩列表,如果不能容纳,才会新建一个新的 quicklistNode 结构