动态字符串SDS

基本概括

Redis中保存的Key是字符串,value往往是字符串或者字符串的集合。可见字符串是Redis中最常用的一种数据结构。

但Redis没有直接使用C语言中的字符串,因为C语言字符串存在很多问题(C语言中实际上没有字符串,本质上是字符数组):

image-20230612223142000

C语言中字符串(字符数组)以'\0'标识字符串结束,如果字符串中本身就包含’\0’就会有问题,另外:

  • 获取字符串长度的需要通过运算
  • 非二进制安全(以’\0’标识结束)
  • 不可修改(拼接字符串需额外申请新的空间)

因此,Redis构建了一种新的字符串结构,称为简单动态字符串Simple Dynamic String),简称 SDS

执行 set name likelong 命令
实际上Redis将在底层创建两个SDS,其中一个是包含“name”的SDS,另一个是包含“likelong”的SDS。

结构如下:

动态字符串SDS_第1张图片

上述最大保存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_第2张图片

动态扩容

SDS之所以叫做动态字符串,是因为它具备动态扩容的能力。

例如一个内容为“hi”的SDS:

image-20230612224743814

此时要给SDS追加一段字符串“,Amy”,这里空间不够首先会申请新内存空间:

动态扩容分以下两种情况:

  1. 如果新字符串小于1M,则新空间为扩展后字符串长度的两倍+1
  2. 如果新字符串大于1M,则新空间为扩展后字符串长度+1M+1。称为内存预分配

(实际使用内存比真实分配内存小一些)

为什么要这么做?申请内存这个动作非常消耗资源(用户态和内核态之间切换)预分配提高性能

扩容后变成这样:

image-20230612225157106

(alloc长度不包含’\0’)

优点

① 获取字符串长度的时间复杂度为O(1) (len值)

② 支持动态扩容

③ 减少内存分配次数(内存预分配)

④ 二进制安全(无需考虑结束标识符’\0’影响)

你可能感兴趣的:(#,redis,redis,数据库,缓存,数据结构)