Redis 底层数据结构

2023-12-13 16:32:56

一、简单动态字符串

简单动态字符串(Simple Dynamic String,简称SDS)是Redis底层实现中使用的一种字符串表示方式,它具有自我调整和内存优化功能。SDS的结构如下:

struct sdshdr {
	// 记录buf数组中已使用字节的数量
	// 等于SDS所保存字符串的长度
	int len;
	//记录buf数组中未使用字节的数量
	int free;
	// 字符数组,用于保存字符串
	char buf[];
}

SDS遵循C字符串以空字符结尾的惯例,保存空字符的1字节空间不计算在SDS的len属性里面

1.1 内存管理

Redis对SDS的内存管理进行了优化。当字符串长度小于16个字节时,Redis会尝试将字符串直接存储在SDS结构中。如果字符串长度超过16个字节,Redis会使用额外的内存来存储字符串。此外,当未使用的空间大于16个字节时,Redis会尝试回收未使用的空间,以减少内存浪费。

1.2 简单动态字符串与C字符串的区别

1.2.1 常数复杂度获取字符串长度

C字符串并不记录自身的长度信息,获取一个C字符串的长度,必须遍历整个字符串,对遇到的字符进行计数,直到遇到代表字符串结尾的空字符为止,复杂度为O(n);而SDS在len属性中记录了SDS的本身长度,复杂度为O(1)。

1.2.2 杜绝缓冲区溢出

C字符串不记录自身长度容易造成缓冲区溢出,SDS的空间分配策略完全杜绝了发生缓冲区的可能性。当SDS API需要对SDS进行修改时,API会先检查SDS的空间是否满足修改所需的要求,如果不满足的话,API会自动将SDS的空间扩展至执行修改所需的大小,然后才执行实际的修改操作。

1.2.3 减少修改字符串时带来的内存重分配次数

通过未使用空间,SDS实现了空间预分配和惰性空间释放两种优化策略:

  1. 空间预分配,当SDS的API对一个SDS进行修改,并且需要对SDS进行空间扩展的时候,程序不仅会为SDS分配修改所必须要的空间,还会为SDS分配额外的未使用空间
  2. 惰性空间释放,当SDS的API需要缩短SDS保存的字符串时,程序并不会立即使用内存分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录起来,等待将来使用。
1.2.4 二进制安全

C字符串中的字符必须符合某种编码,并且除了字符串的末尾之外,字符串里面不能包含空字符。Redis使用字节数组保存一系列的二进制数据,使用len属性的值而不是空字符判断字符串是否结束。

1.2.5 兼容部分C字符串函数

1.3 常用API

sdsnew(const char *init):创建一个新的SDS,并以C字符串init进行初始化。
sdsempty():创建一个空的SDS。
sdslen(const sds s):返回SDS中存储的字符串的长度。
sdsavail(const sds s):返回SDS中未使用的字节数。
sdsdup(const sds s):复制一个SDS,并返回副本。
sdscpy(sds s, const char *t):将C字符串t复制到SDS s 中。
sdscat(sds s, const char *t):将C字符串t追加到SDS s 的末尾。
sdscatsds(sds s, const sds t):将另一个SDS t 追加到SDS s 的末尾。
sdscmp(const sds s1, const sds s2):比较两个SDS的内容。
sdsfree(sds s):释放一个SDS的内存。

二、链表

Redis的链表是一种双向链表,每个节点都包含了一个指向下一个节点和上一个节点的指针。这种设计使得Redis的链表具有高效的插入、删除和搜索操作。
节点结构:每个节点都包含了一个指向下一个节点的指针(next)和一个指向前一个节点的指针(prev)。此外,节点还包含了存储数据的空间。

typedef struct listNode {
	// 前置节点
	struct listNode *prev;
	// 后置节点
	struct listNode *next;
	// 节点的值
	void *value;
}

链表结构:多个节点组合在一起,形成了Redis中的链表。链表的头部节点包含了一个指向第一个节点的指针(head)和一个指向最后一个节点的指针(tail)。

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;

2.1 链表的特性

  • 双端:链表节点带有prev和next指针,获取某个节点的前置节点和后置节点的复杂度都是0(1)。
  • 无环:表头节点的prev指针和表尾节点的next指针都指向NULL,对链表的访问以NULL为终点。
  • 带表头指针和表尾指针:通过list结构的head指针和tail指针:程序获取链表的表头节点和表尾节点的复杂度为O(1)。
  • 带链表长度计数器:程序使用list结构的1en属性来对list持有的链表节点进行计数,程序获取链表中节点数量的复杂度为O(1)。
  • 多态:链表节点使用void*指针来保存节点值,并且可以通过list结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。

三、字典

Redis的字典是一个哈希表,用于存储键值对数据。在Redis中,字典的键是唯一的,而值可以是任意类型。与传统的哈希表不同,Redis的字典提供了丰富的操作命令和特性,使得我们能够更加方便地管理和查询数据。

3.1 数据结构

Redis字典所使用的哈希表的结构定义如下:

typedef struct dictht {
	// 哈希表数组
	dictEntry **table;
	// 哈希表大小
	unsigned long size;
	// 哈希表大小掩码,用于计算索引值,总是等于 size-1
	unsigned long sizemask;
	// 该哈希表已有节点的数量
	unsigned long used;
} dictht;

哈希表节点使用dictEntry结构标识,每个dictEntry结构都保存着一个键值对:

typedef struct dictEntry {
	// 键
	void *key;
	// 值
	union {
		void *val;
		uint64_t u64;
		int64_t s64;
	} v;
	// 指向下个哈希表节点,形成链表
	struct dictEntry *next;
} dictEntry;

Redis中的字典由dict结构表示:

typedef struct dict{
	// 类型特定函数
	dictType *type;
	// 私有数据
	void *privdata;
	// 哈希表
	dictht ht[2];
	// rehash索引,当rehash不再进行时,值为-1
	int trehashidx;
} dict;

文章来源:https://blog.csdn.net/qq_43217697/article/details/134796761
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。