基本概括
Redis中保存的Key是字符串,value往往是字符串或者字符串的集合。可见字符串是Redis中最常用的一种数据结构。
但Redis没有直接使用C语言中的字符串,因为C语言字符串存在很多问题(C语言中实际上没有字符串,本质上是字符数组):
C语言中字符串(字符数组)以'\0'
标识字符串结束,如果字符串中本身就包含’\0’就会有问题,另外:
- 获取字符串长度的需要通过运算
- 非二进制安全(以’\0’标识结束)
- 不可修改(拼接字符串需额外申请新的空间)
因此,Redis构建了一种新的字符串结构,称为简单动态字符串(Simple Dynamic String),简称 SDS
。
执行 set name likelong 命令
实际上Redis将在底层创建两个SDS,其中一个是包含“name”的SDS,另一个是包含“likelong”的SDS。
结构如下:
上述最大保存255(2^8 - 1)个字节字符数组
Redis源码:多种类型SDS,比较常用的还是上面的类型,无需占用多余内存
sds.h文件
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[];
};
例如,一个包含字符串“name”的sds结构如下:
动态扩容
SDS之所以叫做动态字符串,是因为它具备动态扩容的能力。
例如一个内容为“hi”的SDS:
此时要给SDS追加一段字符串“,Amy”,这里空间不够首先会申请新内存空间:
动态扩容分以下两种情况:
- 如果
新字符串小于1M
,则新空间为扩展后字符串长度的两倍+1
; - 如果
新字符串大于1M
,则新空间为扩展后字符串长度+1M+1
。称为内存预分配。
(实际使用内存比真实分配内存小一些)
为什么要这么做?申请内存这个动作非常消耗资源
(用户态和内核态之间切换)预分配提高性能
扩容后变成这样:
(alloc长度不包含’\0’)
优点
① 获取字符串长度的时间复杂度为O(1) (len值)
② 支持动态扩容
③ 减少内存分配次数(内存预分配)
④ 二进制安全(无需考虑结束标识符’\0’影响)