redis源码解析(二)——SDS(简单动态字符串)

本文深入探讨Redis中SDS(简单动态字符串)的设计,对比C语言中的字符串,分析SDS的特性,如空间预分配和惰性释放策略。详细解读`sds.h`中的结构体、宏定义以及`sds.c`中的关键函数,包括`sdsIncrLen`、`sdscatvprintf`等。
摘要由CSDN通过智能技术生成

版本:redis - 5.0.4
参考资料:redis设计与实现
文件:src下的sds.c sds.h

一、c语言中的字符串 vs SDS

c中无字符串,只有字符数组

//c语言中定义一个字符串
//申请一段连续数组空间,s指向首地址,每个单元存储一个字符,'\0'为结束标志
// {'h','e','l','l','o','\0'}
char* s = "hello"
//sds
struct __attribute__ ((__packed__)) sdshdr8 {
   
    uint8_t len; //已使用的数组长度,不包括零结束标志
    uint8_t alloc; //总长度,不包括零结束标志
    unsigned char flags; //表示选择哪种sds header
    char buf[];//用于存储字符串
};
  1. 获取字符串长度需要运算:没有字段存储字符串长度,需要运算。申请空间大小≠字符串长度。
  2. 非二进制安全:C语言中,以空字符‘\0’作字符串结束标志。
    • sds延续了这种做法,兼容部分c字符串函数。
    • 字符串里不能包含空字符。sds以处理二进制的方式处理数据。
  3. 不可修改:sds同样用字符数组存储字符串。字符串修改时,字符串增长或缩短,内存需要重分配
    • 空间预分配:字符串增长时并需要空间扩展时,若增长后的 len < 1M,则再分配同样大小的avail空间,也就是说buf数组的实际长度变为len*2 + 1字节(一字节存储空字符)。若增长后的len >= 1M,则再分配1M大小的avail空间,buf数组的实际长度变为len + 1M + 1byte;
    • 惰性空间释放:字符串缩短时,并不释放多出来的字节,而是记录下来,等待将来使用。
c字符串 sds
获取字符串长度的复杂度为 O(N) 获取字符串长度的复杂度为 O(1)
API 是不安全的,可能会造成缓冲区溢出 API 是安全的,不会造成缓冲区溢出
修改字符串长度 N 次必然需要执行 N 次内存重分配 修改字符串长度 N 次最多需要执行 N 次内存重分配 ,内存分配次数减少,可动态扩容
只能保存文本数据 可以保存文本或者二进制数据
可以使用所有 <string.h> 库中的函数 可以使用一部分 <string.h> 库中的函数

二、sds.h

1、sdshdr
header

sdshdr(sds header)用于控制字符串,存储了字符数组buf,字符串长度len(实际使用的的长度),总长度alloc(数组长度减去存放空字符的1的字节)。

由于字符串长度不同,所以表示长度的len和alloc所需要的大小也不同,有四种:

由此产生了五种header ,通过选择合适的header,用来节省空间。并且header里需要存储一个flag,表明选择了哪种header。flag占一个字节,8位,而五种header只需用三位表示就行,所以还有五位没有用。

 __attribute__((packed)) :编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,是GCC特有的语法。
 __attribute__可以设置函数属性、变量属性和类型属性
 使用格式为:__attribute__ ((attribute-list));要求放于声明的尾部“;”之前。

关于 __attribute__的更多内容

typedef char *sds;

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
 /*注意: 永远不会使用 sdshdr5, 我们只需直接访问标志字节。但是, 这里是文件类型 5 SDS 字符串的布局。*/
struct __attribute__ ((__packed__)) sdshdr5 {
   
    unsigned char flags; //3位存header类型, (高)5位存字符长度
    char buf[];//字符最长为2^5 = 31
    //所以sdshdr5 没有alloc,
};

struct __attribute__ ((__packed__)) sdshdr8 {
   
    uint8_t len; //已使用的数组长度
    uint8_t alloc; //总长度不包括零结束标志
    unsigned char flags; //表示选择哪种sds header
    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[];
};

//一共五种header 用三位表示
#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

sds的存储


由于在一个结构体里面,而且关闭了自动对齐,此时的header内的各个部分都是紧紧挨在一起的,没有多余空间。
在一开始申请空间时,buf只是一个标志,证明那有一个数组,但并不给其分配空间,只给其他的部分分配空间。

操作宏定义
/**
* 宏定义中的##是将两个符号连接成一个,如T=5时,sdshdr和T合成sdshdr5
* 将该地址赋给了一个新变量sh,得到了一个指向header的指针。
* T是type(哪一种header),s是字符串指针(一个字符串的首地址)
* sizeof(struct sdshdr##T)是一个header大小(不包括buf),
* (s)-(sizeof(struct sdshdr##T))就是首地址减header的大小,即header的首地址。
*/
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));

//得到header的首地址
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))

//f为flag, f左移三位,得到高五位,就是字符长度
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)

在这里插入图片描述

2、static inline 函数

下面,在.h文件中定义了一些函数,这些函数是static inline的。
inline:把函数指定为内联函数

#define SDS_TYPE_MASK 7//掩码
#define SDS_TYPE_BITS 3

//得到s的长度
static inline size_t sdslen(const sds s) {
   
    unsigned char flags = s[-1];

    //flag和掩码相与,只留后三位,判断类型
    //根据类型,得到header,获取长度
    switch(flags&SDS_TYPE_MASK) {
   
        case SDS_TYPE_5:
            return SDS_TYPE_5_LEN(flags);
        case SDS_TYPE_8:
            return SDS_HDR(8,s)->len;
        case SDS_TYPE_16:
            <
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值