Redis 底层数据结构
一、简单动态字符串
简单动态字符串(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实现了空间预分配和惰性空间释放两种优化策略:
- 空间预分配,当SDS的API对一个SDS进行修改,并且需要对SDS进行空间扩展的时候,程序不仅会为SDS分配修改所必须要的空间,还会为SDS分配额外的未使用空间
- 惰性空间释放,当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;
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!