源码笔记1.简单动态字符串SDS

数据结构:

/*
 * simple dynamic string
 */
typedef char *sds;

/*
 * simple dynamic string header
 */
struct sdshdr
{
    int len; // buf数组中已使用的长度
    int free; // buf数组中剩余长度
    char buf[]; // 存放字符的数组  知识点关键词: C99 柔性数组结构成员
};

显而易见,sds本身是个char类型的数据。而char类型和C语言的字符串类型完全兼容。
这个sds header的结构体,可以理解成header包含len和free信息,数据在buf中。sds实际是指向sdshdr结构体的字符串空间 char buf[]。

众所周知,C语言中字符串的结尾是’\0’,那如果数据本身有0,会导致字符串读到0就认为结束了。
这里可以理解到,为何Redis的sds是二进制安全的,sds在真正读数据时不会因为数据中的0导致截断,因为header有长度信息len,可以读到完整数据。

关键方法:

sds sdsnewlen(const void *init, size_t initlen) {
    struct sdshdr *sh;
    // 根据init判断是否初始化分配的内存空间 
    // zmalloc zcalloc 是redis自身实现的内存分配方法
    if (init) {
        sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
    } else {
        sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
    }
    if (sh == NULL) return NULL; // 分配失败
    sh->len = initlen; // 初始长度
    sh->free = 0; // 新创建的sds没有free空间
    // 如果有初始化内容,将初始化数据复制到sdshdr的buf数组
    if (initlen && init)
        memcpy(sh->buf, init, initlen);
    // buf数组以 \0 结尾,和C的字符串一致
    sh->buf[initlen] = '\0';
    // 返回buf的指针,sds指针,后面会有sdshdr的获取方式介绍
    return (char*)sh->buf;
}

这是创建sds的方法。注意初始化buf时,结尾仍然是C接口兼容的’\0’结尾。

static inline size_t sdslen(const sds s)
{
    struct sdshdr *sh = (void *)(s - (sizeof(struct sdshdr)));
    return sh->len;
}

这是获取sds真正长度的方法。

在C99标准中,新增了一个标准:incomlete type,形式同int a[]; 中文翻译为柔性数组,数组大小未知,长度为0。结构体的最后一个元素可以是柔性数组,柔性数组成员前至少有一个其他成员。(理解:一个结构体最多只能有一个柔性数组,还得放在最后。)使用malloc函数进行内存分配时,分配的内存应该大于结构的大小,以适应柔性数组的预期大小。(理解:结构体的大小不包含柔性数组的大小,但是分配内存时要注意给它分配。结合sds来讲,sds不占header的空间,只是一个header的地址偏移量,内存上是连续的。)

以上获取sds长度方法,其中 s - (sizeof(struct sdshdr)) 是关注点。源码中普遍使用这种做法,通过sds的绝对地址去计算header的地址。
sizeof(struct sdshdr)实际上等于sizeof(len) + sizeof(free), 柔性数组buf的大小不计入struct。
sdshdr的内存分布是连续的, {[len][free]}[buf...]
sds指向buf, 所以s的地址减去struct的size,就是sdshdr的地址。

sdsfree 释放sds空间 zfree(header地址)
sdsclear 惰性删除buf内容,len=0,buf[0] = ‘\0’

sdsmakeRoomFor 增加预分配空间。如果新长度小于1M就按照新长度*2分配,新长度不小于1M就再增加1M。
sdsRemoveFreeSpace 回收free空间。
sdsAllocSize 获取sds占用内存字节数。sizeof(header)+len+free+1。
sdsIncrLen 增加incr参数的长度到len,较少free对应长度。
sdsgrowzero 将sds扩充到指定长度,未使用的用0填充。
sdscatlen sdscat sdscatsds 追加长度为len的字符串t到sds的末尾。
sdscpylen sdscpy 将字符串的钱len个字符复制到sds中。

#define SDS_LLSTR_SIZE 21

long long string 长度21 (unsigned longlong最大值是20位; longlong是19位,带符号是20位,这边设置成21位有1位空余,用来放字符串结尾’\0’)

sdsll2str 将long long转化为字符串存储。返回长度。
sdsull2str 将unsingned long long转化为字符串存储。返回长度。
sdsfromlonglong 根据longlong值,创建sds。
sdscatvprintf sdscatprintf 格式化的字符串连接到sds上,用到了vsnprintf。(不定长参数…,用va_list读取。)
sdacatfmt 这是redis自己实现的sprintf方法,而不是上面用的libc库的方法。

 /*
 * %s - C String
 * %S - SDS string
 * %i - signed int
 * %I - 64 bit signed integer (long long, int64_t)
 * %u - unsigned int
 * %U - 64 bit unsigned integer (unsigned long long, uint64_t)
 * %% - Verbatim "%" character.
 */
 sds sdscatfmt(sds s, char const *fmt, ...)

实现过程朴实无华,每次扩容一个字符,将fmt下个字符拷贝到s,遇到格式符再分情况扩容拷贝。

sdstrim 对sds左右进行清理,去掉指定字符。用到了strchr来判断字符是否在指定字符串中,用到了memmove来调整最终字符串。

memmove是比 memcpy更安全的方法。如果目标区域和源区域有重叠的话,memmove()
能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中,复制后源区域的内容会被更改。如果目标区域与源区域没有重叠,则和
memcpy() 函数功能相同。

sdsrange 截取sds字符串中段区间[start, end]
sdstolower 将 sds 字符串中的所有字符转换为小写
sdstoupper 将 sds 字符串中的所有字符转换为大写
sdscmp 类似strcmp 的 sds 版本 (int :相等返回 0 ,s1 较大返回正数, s2 较大返回负数)
sdssplitlen 使用分隔符对sds进行分割,返回sds数组
sdsfreesplitres 用来释放sdssplitlen产生的sds数组
sdscatrepr 将长度为 len 的字符串 p 以带引号的格式,追加到给定 sds 的末尾

sdscatrepr里有个isprint函数,判断是否是可打印字符。查看ctype.h源码可知是一个宏定义:

# define isprint(c)	__isctype((c), _ISprint)
# define __isctype(c, type) \
  ((*__ctype_b_loc ())[(int) (c)] & (unsigned short int) type)
  /*
_ISprint = _ISbit (6); //其实就是位移操作。根据系统是little endian还是big endian返回不同位移结果。
关键的__ctype_b_loc数组,个人测试结果是针对ASCII字符做了标记,这样能通过位运算把ASCII码做分组。
isprint的ASCII码范围是[0x20,0x7f)
*/

sdssplitargs 将文本分割成多个参数。应用于长文本或者配置文件。例如"ip 127.0.0.1\r\nport 6793\r\n" => ["ip", "127.0.0.1", "port": "6793"]
sdsmapchars 对sds做字符替换,在from中出现的替换为to中的字符。
sdsjoin 对字符串数组做连接,可以由分隔符。

sds.c最后是一份单元测试代码,用宏定义来做开关。

SDS印象最深的点:

  • typedef char *sds;
  • SDS的数据结构,柔性数组的使用。
  • struct sdshdr *sh = (void *)(s -(sizeof(struct sdshdr))); 通过sds绝对地址获取header结构指针。
  • 二进制安全,部分接口兼容C字符串接口函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值