Redis(二):自定义数据结构

前言

上一篇介绍了 Redis 是如何存储和索引数据的,这一篇将介绍 Redis 定义的八大数据结构,这些高效的数据结构,不仅帮助 Redis 实现了高性能,更能够应用于各种场景。

八大数据结构如下:SDS、双向链表、压缩列表、哈希表、整数集合、跳表、quicklist、listpack。

SDS

simple dynamic string,简单动态字符串。

Redis 是用 C 语言实现的,但是它没有直接使用 C 语言的 char* 字符数组来实现字符串,而是自己封装了一个名为 SDS 的数据结构来表示字符串,主要是因为 C 语言提供的字符串实现方式有部分缺陷。

C语言字符串缺陷

C 语言的字符串其实就是一个字符数组,即数组中每个元素是字符串中的一个字符。最后一个字符是“\0”,表示字符串的结束

缺陷:

  • C 语言获取字符串长度的时间复杂度是 O(N),需要遍历数组寻找“\0”的位置;
  • 字符串的结尾是以 “\0” 字符标识,所以字符串里面不能含有 “\0” 字符,否则最先被程序读入的 “\0” 字符将被误认为是字符串结尾,这个限制使得 C 语言的字符串只能保存文本数据,不能保存像图片、音频、视频文化这样的二进制数据
  • C 语言标准库中字符串的操作函数既不高效又不安全,容易导致缓冲区溢出;
结构设计

SDS 结构中的每个成员变量如下:

  • len,记录了字符串长度,占 4 个字节。这样获取字符串长度的时候,只需要返回这个成员变量值就行,时间复杂度只需要 O(1);
  • alloc,分配给字符数组的空间长度,占 4 个字节。用于自动扩容;
  • flags,用来表示不同类型的 SDS。一共设计了 5 种类型,分别是 sdshdr5、sdshdr8、sdshdr16、sdshdr32 和 sdshdr64;
  • buf[],字符数组,用来保存实际数据。不仅可以保存字符串,也可以保存二进制数据。为了兼容部分 C 语言标准库的函数, 结尾还是会加上 “\0” 字符,额外占 1 个字符。

flags 表示的是 SDS 类型,不同类型的区别在于,它们数据结构中的 len 和 alloc 成员变量的数据类型不同。之所以设计不同类型的结构体,是为了能灵活保存不同大小的字符串,从而有效节省内存空间。

除了设计不同类型的结构体,Redis 在编程上还使用了专门的编译优化来节省内存空间,即在 struct 声明了 __attribute__ ((packed)) ,它的作用是:告诉编译器取消结构体在编译过程中的优化对齐,按照实际占用字节数进行对齐

默认情况下,编译器是按照字节对齐的方式给变量分配内存的,比如一个 char 类型变量和一个 int 类型变量,会被分配到 8 个字节(int 占四个字节,char 占一个字节但是会被分配到四个字节)。

SDS 相比于 C 的原生字符串:

  • SDS 不仅可以保存文本数据,还可以保存二进制数据。因为 SDS 使用 len 属性的值而不是空字符来判断字符串是否结束,并且 SDS 的所有 API 都会以处理二进制的方式来处理 SDS 存放在 buf[] 数组里的数据。所以 SDS 不光能存放文本数据,而且能保存图片、音频、视频、压缩文件这样的二进制数据。
  • SDS 获取字符串长度的时间复杂度是 O(1)。因为 C 语言的字符串并不记录自身长度,所以获取长度的复杂度为 O(n);而 SDS 结构里用 len 属性记录了字符串长度,所以复杂度为 O(1)
  • Redis 的 SDS API 是安全的,拼接字符串不会造成缓冲区溢出。因为 SDS 在拼接字符串之前会检查 SDS 空间是否满足要求,如果空间不够会自动扩容,所以不会导致缓冲区溢出的问题。
自动扩容

在修改字符串的时候,可以通过 alloc - len 计算出剩余的空间大小,可以用来判断空间是否满足修改需求,如果不满足的话,就会自动将 SDS 的空间扩展至执行修改所需的大小。就不会出现前面所说的缓冲区溢出的问题。

当判断出缓冲区大小不够用时,Redis 会自动将扩大 SDS 的空间大小:

  • 如果所需的 SDS 长度小于 1 MB,那么最后的扩容是按照翻倍扩容来执行的,即 2 倍的 newlen
  • 如果所需的 SDS 长度超过 1 MB,那么最后的扩容长度应该是 newlen + 1MB

在扩容 SDS 空间之前,SDS API 会优先检查未使用空间是否足够,如果不够的话,API 不仅会为 SDS 分配修改所必须要的空间,还会给 SDS 分配额外的「未使用空间」,从而减少内存分配次数。

双向链表

C 语言本身没有链表这个数据结构的,所以 Redis 自己设计了一个链表数据结构。

typedef struct list {
   
   
    //链表头节点
    listNode *head;
    //链表尾节点
    listNode *tail;
    //节点值复制函数
    void *(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值