在Redis中,使用简单动态字符串(simple dynamic string)作为默认字符串表示。
SDS 结构如下:
struct sdshdr {
int len; // 字符串长度 单位:字节
int free; // 未使用长度
char buf[]; // 字符串内容
}
SDS保留C字符串以空字符结尾的惯例,结尾所需空间不计算在len长度里。
同时,也因为保留C字符串空字符结尾的惯例,能够兼容部分C字符串函数,避免了不必要的代码重复。
Redis中的链表是双端、无环( head 节点的 prev 指针和 tail 节点的 next 指针都指向 null )、具有多态性(链表节点使用 void* 来保存值)的链表。
// 定义链表节点
typedef struct listNode {
struct listNode *prev; // 前节点
struct listNode *next; // 后节点
void *value; // 值
} listNode;
// 定义链表
typedef struct list {
listNode *head; // 头结点
listNode *tail; // 尾结点
unsigned long len; // 链表长度
void *(*dup)(void *ptr); // 节点值复制函数
void (*free)(void *ptr); // 节点值释放函数
int (*match)(void *ptr,void *key); // 节点值对比函数
} list;
字典是一种用于保存键值对的抽象数据结构。
Redis字典使用哈希表作为底层实现,一个哈希表里面可以有多个节点,而每个节点就保存了字典中的一个键值对。
// 哈希表节点
typedef struct dictEntry {
void *key; // 键
union { // 值 可以是指针/uint64_t整数/int64_t整数
void *val;
uint64_t u64;
int64_t s64;
} v;
struct dictEntry *next; // 下个节点
} dictEntry;
// 哈希表
typedef struct dictht {
dictEntry **table; // 哈希表数组
unsigned long size; // 哈希表大小
unsigned long sizemask; // 哈希表大小掩码 和哈希值一起决定一个键应该放在table的哪个索引上
unsigned long used; // 已有节点数量
} dictht;
// 字典
typedef struct dict {
dictType *type; // 类型特定函数
void *privdata; // 私有数据
dictht ht[2]; // 哈希表
int rehashidx; // rehash进度 若当前不在rehash,则为-1
} dict;
从上面的定义我们可以看到字典的哈希表 ht 有2个项,正常情况下只会使用 ht[0] , rehashidx 的值也默认为-1。只有在 rehash 的时候会使用 ht[1] ,并用 rehashidx 记录当前 rehash 的进度。
在进行 rehash 的时候,不是马上将 ht[0] 的哈希节点重分配到 ht[1] 的,而是渐进完成的,详细步骤如下:
如下图,为 ht[1] 分配完空间后,将 rehashidx改为0,然后在第一次操作字典的时候,会将 ht[0] 里哈希表索引为0的所有哈希节点(k2)进行 rehash 到 ht[1],然后修改rehashidx=rehashidx+1=1;在第二次操作字典的时候,会将 ht[0] 里哈希表索引为1的所有哈希节点(k0)进行 rehash 到 ht[1],然后修改rehashidx=rehashidx+1=2;一直重复直到遍历完哈希表。
跳跃表是一种有序的数据结构,通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。
// 跳跃表节点
typedef struct zskiplistNode {
struct zskiplistNode *backward; // 后退指针
double score; // 分值
robj *obj; // 成员对象
struct zskiplistLevel { // 层
struct zskiplistNode *forword; // 前进指针
unsigned int span; // 跨度
} level[];
} zskiplistNode;
// 跳跃表
typedef struct zskiplist {
structz skiplistNode *header, *tail; // 头、尾指针
unsigned long length; // 节点数量(除头结点)
int level; // 节点最大层次(除头结点)
}
当一个集合只包含整数值元素,且元素数量不多的时候,Redis 会使用整数集合作为集合键的底层实现。
// 整数集合
typedef struct intset {
uint32_t encoding; // 编码方式 int16_t/int32_t/int64_t
uint32_t length; // 元素数量
int8_t contents[]; // 保存元素数组 从小到大排序
} intset;
若向整数集合新增一个比原本元素的类型都要长的元素的时候,需要先进行升级。具体步骤如下:
好处:提高灵活性(无需重新创建、复制集合),尽可能节约内存(不在一开始就创建最大的内存)
整数集合不支持降级操作,一旦进行了升级,编码就会一直保持升级后的状态,即使原本“大类型”的元素被删除。
当一个列表键只包含少量列表项,并且每个列表项要么是小整数,要么是长度较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。
压缩列表具体组成如下:
压缩节点具体组成如下:
通过 previous_entry_length 和当前节点的起始地址,能够计算出前一个节点的起始地址。
通过 encoding 的前两位来区分是字节数组编码,还是整数编码。整数编码(1字节)前2位是 11,此外都是字节编码。在字节编码中,又根据编码长度进行细分,00表示1字节的字节数组编码;01表示2字节的字节数组编码;10表示5字节的字节数组编码。
阅读文章:https://blog.csdn.net/weixin_45729809/article/details/123789656
参考《Redis设计与实现》第一部分:数据结构与对象