(课程总结自b站黑马程序员课程)
一、引言
Redis中保存的Key是字符串,value往往是字符串或者字符串的集合。可见字符串是Redis中最常用的一种数据结构。
不过Redis没有直接使用C语言中的字符串,因为C语言字符串存在很多问题:
①获取字符串长度的需要通过运算
②非二进制安全:指‘/0’字符的读取问题。
③不可修改 Redis构建了一种新的字符串结构,称为简单动态字符串(Simple Dynamic String),简称SDS。
二、源码分析
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* used */
uint8_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* used */
uint16_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* used */
uint32_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; /* used */
uint64_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
#define SDS_TYPE_5 0
#define SDS_TYPE_8 1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4
以sdshdr8为例进行分析:
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; //已使用字符串字节数,不包括结束标志
uint8_t alloc; //申请总字节数,不包括结束标志
unsigned char flags; //SDS头信息
char buf[];
};
这个的uint8_t len记录的字节长度,8位二进制的最大值为255,也就是可以记录最大255字节数的字符串。c语言中char的占用的字节数为1,也就是sdshdr8可以记录长度为255的字符串。
三、结构分析
例如,一个包含字符串“name”的sds结构如下:
SDS之所以叫做动态字符串,是因为它具备动态扩容的能力,例如一个内容为“hi”的SDS:
假如我们要给SDS追加一段字符串“,Amy”,这里首先会申请新内存空间:
如果新字符串小于1M,则新空间为扩展后字符串长度的两倍+1;
如果新字符串大于1M,则新空间为扩展后字符串长度+1M+1。称为内存预分配。
(申请分配内存需要Linux从用户态切换到内核态,这个过程需要运用的资源相当多,内存预分配可以在很大程度上节省资源分配)
四、优点
①获取字符串长度的时间复杂度为O(1)。
②支持动态扩容。
③减少内存分配次数。
④二进制安全。