SDS动态字符串
SDS简单动态字符串,是Redis的默认字符串,内部存储结构如下(buf不是指针,箭头仅代表buff数组存放的字符串):
比起C字符串,SDS具有的一下优点
1、常数复杂度获取字符串长度
2、杜绝缓冲区溢出
3、二进制安全
4、兼容部分C字符串函数
5、减少修改字符串时带来的内存重新分配次数
1)空间预分配,当len < 1M,分配修改后总长度的二倍,若修过后>=1M,预留1M空间
2)惰性空间释放,只是标记free,并不释放空间
字符串对象
对于Redis数据库保存的键值对来说,键总是一个字符串对象,而值则可以是字符串对象、列表对象、哈希对象、集合对象、有序集合对象的其中一种。字符串对象的编码可以是 int、raw、embstr,其中raw和embstr都是采用的SDS结构,区别在于embstr使用一块内存存储redisobject结构和SDS结构,而raw开辟两次内存,分别存储两个数据结构。
int编码
如果一个字符串对象保存的是整数值,并且这个整数值可以使用long类型存储,那么字符串对象会将value保存在redisObject结构的ptr属性里,并将字符串对象的编码设置为int。
举例说明,set num 123456,底层就是把ptr当成long类型存储字符串“123456”,截图如下:
int编码的字符串对象底层存储如下:
embstr编码
如果字符串对象保存的是一个字符串值,并且这个字符串值的长度小于等于44字节(redis_version:6.0.8),那么字符串对象使用embstr编码的方式来保存这个字符串值。embstr编码是专门用于保存短字符串的一种优化编码方式,通过调用一次内存分配函数来分配一块连续的空间,空间中依次包含redisObject和SDS两个结构,减少了分配释放内存次数,有效利用了cpu缓存。
举例说明:set msg 1.1,redis会把浮点数当成字符串存储,当运算时会强转类型,计算后,再次转成字符串存储,截图如下:
embstr编码的字符串对象底层存储如下:
raw编码
如果字符串对象保存的是一个字符串值,并且这个字符串大于44字节,那么字符串对象将使用SDS来保存这个字符串值,并将编码设置为raw。
举例说明 set msg 111111111111111111111111111111111111111111113,截图如下:
raw编码的字符串对象底层存储如下:
#### 编码自动转换
- 对应int编码的字符串对象,如果我们执行一些命令,使这个对象保存的不再是整数值,而是字符串值(
浮点数也是字符串值),则自动转为raw - 因为embstr编码是只读的,当我们对embstr编码的对象执行任何修改命令时,程序会将对象转为raw,然后再执行修改命令
Redis字符串常用命令
-------------------------------更新 2020.11.18----------------------------
Redis进一步对SDS优化
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[]; //柔性数组,本身不占空间,它只是一个偏移量,必须在结构体的最后一个。sizeof(struct sdshdr5) = 1
};
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[];
};
packed 告知编译器不要优化,取消内存对齐的优化。当新建字符串对象时,根据字符串长度判断使用那种类型,主要为了进一步减少内存的使用,比如len、alloc在uint8_t占用两个字节,而在uint32_t需要占用8个字节,能表达的字符串长度越大,len和alloc占用的字节也越多,字符串对象作为使用频率最高的对象,这种优化也是很可观。判断创建字符串时选择哪种类型函数如下:
static inline char sdsReqType(size_t string_size) {
if (string_size < 1<<5) // 2 的 5 次方等于32
return SDS_TYPE_5;
if (string_size < 1<<8) // 2 的 8 次方等于256,下面依次类推
return SDS_TYPE_8;
if (string_size < 1<<16)
return SDS_TYPE_16;
#if (LONG_MAX == LLONG_MAX)
if (string_size < 1ll<<32)
return SDS_TYPE_32;
return SDS_TYPE_64;
#else
return SDS_TYPE_32;
#endif
}
当新建字符串小于32字节,符合 struct sdshdr5时,flags 低三位保存SDS_TYPE_5类型,高五位记录initlen,比如保存“hello”,需要开辟sizeof(struct sdshdr5) + strlen("hello") + 1一共7个字节。