redis中的字符串底层是使用自定义的SDS数据结构。
Redis是基于C语言编写,而C语言中的字符串其实就是字符数组,它除了二进制不安全外,还缺少额外信息,无法支持一些高级特性(扩容、越界判断等),于是Redis在字符数组的基础之上做了少了变更,形成了SDS结构。
Redis3.2之前的版本中SDS都是如下结构
struct sds {
int len; // buf中已占用字节数
int free; // buf中剩余可用字节数
char buf[]; // 数据空间
};
对比C中的字符串,SDS的有点主要有:
此外,SDS还将柔性数组buf的指针直接暴露,兼容了C语言处理字符串的各种函数
柔性数组(Flexible Array Member,亦称为可变长数组成员)是一种在 C99 标准引入的结构体特性。它允许结构体的最后一个元素定义为一个未指定大小的数组,这样可以在运行时动态地为这个数组分配内存。柔性数组提供了一种灵活的方法来处理变长数据结构,使得内存分配和管理更加方便。
Redis以高性能著称,而其高性能的核心就是完全的内存操作,内存是Redis的立身之本,所以Redis对于内存的使用非常精细,一个典型的例子就是SDS根据不同的数据长度又分成了5种类型,尽量用最合理的数据类型来表示额外的空间信息
一字节有8位,其中3位可以表示小于8的数据范围,5位可以表示小于32的数据范围,Redis将SDS划分成了5种类型,刚好可以用高三位表示,那么对于长度小于32的短字符串,Redis使用了一个字节的标记字段就保存了类型和长度两个信息,其结构如下
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
};
对于长度大于等于32的字符串,无法继续使用一个字节的标记字段表示,只能额外拿出两个字段来标记长度和容量,不同的是根据范围的不同,这两个字段使用的数据类型会有差异,其具体结构如下
struct __attribute__((__packed__))sdshdr8 {
uint8_t len; /* 已使用长度,用1字节存储 */
uint8_t alloc; /* 总长度,用1字节存储*/
unsigned char flags; /* 低3位存储类型,高5位预留 */
char buf[]; /*柔性数组,存放实际内容*/
};
struct __attribute__((__packed__))sdshdr16 {
uint16_t len; /*已使用长度,用2字节存储*/
uint16_t alloc; /* 总长度,用2字节存储*/
unsigned char flags; /* 低3位存储类型,高5位预留 */
char buf[]; /*柔性数组,存放实际内容*/
};
struct __attribute__((__packed__))sdshdr32 {
uint32_t len; /*已使用长度,用4字节存储*/
uint32_t alloc; /* 总长度,用4字节存储*/
unsigned char flags; /* 低3位存储类型,高5位预留 */
char buf[]; /*柔性数组,存放实际内容*/
};
struct __attribute__((__packed__))sdshdr64 {
uint64_t len; /*已使用长度,用8字节存储*/
uint64_t alloc; /* 总长度,用8字节存储*/
unsigned char flags; /* 低3位存储类型,高5位预留 */
char buf[]; /*柔性数组,存放实际内容*/
};
不知道你看到这个结构是否会有疑惑:在使用的时候如何能区分出这几种结构呢?
这需要结合一下两点:
sds
给上层返回的是buf[]
的指针,观察上述5种结构,buf[]
的前面都是flags
字段,拿到flags
字段自然就能得知属于那种数据类型__packed__
字段告知编译器1字节对齐,相当于保证flags
和buf[]
是挨着的,保证我们上述的计算方式可以执行原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。