“实在”的 sdshdr 构造
在《Redis 设计与实现》中,提到 sds 的实现构造 sdshdr 是这样的:
struct sdshdr {
// 记录 buf 数组已应用字节的数量
// 等于 SDS 所保留字符串的长度
int len;
// 记录 buf 数组中未应用的字节数
int free;
// 字节数组,用于保留字符串
char buf[];};
这可能是 Redis 以前的版本是这样的,笔者查看的源码是 7.0
在 Redis 7.0 中,sdshdr (在 sds.h 中)构造是这样的:
/* Note: sdshdr5 is never used, we just access the flags byte directly.
* However is here to document the layout of type 5 SDS strings. */
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[];};
依据字符串长度的不同,用来寄存它的 sdshdr 类型也是不同的
在这段代码的上方有一段正文,意思是 sdshdr5 构造是不应用的(网上有人说是应用的,具体能够看【Redis 源码剖析】一个对 SDSHDR5 是否应用的疑难 – SegmentFault 思否)。
接下来咱们剖析除 sdshdr5 之外,其余构造中的各个成员:
len: 已应用的字符串长度;
alloc: 为字符串调配的空间总长度;
flags: 标记以后构造的类型,即以后构造体是 8/16/32/64 位;
flags 只应用低三位字节,高五位是不应用的。三位足够示意 5 个数字,不同的值代表不同的类型,上面是对于不同类型的常量定义:#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
buf: 指向字符串;
当初咱们能够察看到《Redis 设计与实现》中的 sdshdr 与 7.0 的 sdshdr 的区别,多了一个 flag 标识,且并没有 free 属性,而是 alloc 属性,而 alloc – len 即代表 free 的值。
在定义 sdshdr 构造时,咱们发现后面加上了 __attribute__ ((__packed__))
,它的作用是勾销编译器的对齐,即构造 内的成员在内存中是紧凑的。
为什么不内存对齐呢?因为省一点点内存吗?
咱们先看对齐后 SDS_TYPE_8
、SDS_TYPE_16
、SDS_TYPE_32
、SDS_TYPE_64
的内存布局是怎么的:
能够看到不同类型的 sdshdr 对齐的字节数不同,这就让 (char*)buf - 1
无奈让每种 sdshdr 都定位到 flags 的地址,如果想通过 buf 定位到 flags 的地址,须要进行类型判断,并且不同的零碎可能有不同的对齐形式。
那为了这个就舍弃内存对齐从而升高效率吗?
在一篇文章中解释了 Redis 的另外一种内存对齐((3 条音讯) redis 源码解读(一): 根底数据结构之 SDS_czrzchao 的博客 -CSDN 博客):
redis 通过本人在 malloc 等 c 语言内存调配函数上封装了一层zmalloc,将内存调配收敛,并解决了内存对齐的问题。在内存调配前有这么一段代码:
if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \ // 确保内存对齐!
这段代码写的比拟形象,简而言之就是先判断以后要调配的 _n
个内存是否是 long
类型的整数倍,如果不是就在 _n
的根底上加上内存大小差值,从而达到了内存对齐的保障。
尽管设计了 sdshdr 这几种构造,但理论在应用 sds 时,咱们都是应用 sds 的接口来实现对 sds 的批改,并没有间接应用到这些构造。
通过 sds 获取其构造地址
在 sds.h 中,有这样两个宏:
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
对于 sdshdr##T
中的 ##
咱们就不具体解释了,在这里了解成它将 sdshdr
和 T
连贯在一起,即示意不同的 sdshdr 类型
对于 SDS_HDR_VAR(T,s)
,参数 s 是 sdshdr 构造体中的字符串指针,即等价于 buf,参数 T 则是示意不同类型的 sdshdr,取值能够为 8/16/32/64。而后看其实现,struct sdshdr##T *sh
是宏定义的一个变量,void*
是将后果转换为 void*
类型,以便 sh
接管。而 (s) – (sizeof(struct sdshdr##T) ) 则示意指向构造体变量的地址。首先 sizeof(struct sdshdr##T) 计算的大小不蕴含 buf (柔性数组特点),而 s 的指向的地址就跟在 sdshdr 之后(在接下来的 _sdsnewlen 函数中会实现),咱们看图示:
留神,是 (s) - (sizeof(struct sdshdr##T) )
,s 的值是所寄存字符串的起始地址,而 不是 s 的地址。因而,该表达式失去的是构造体变量 sdshdr##T
的起始地址。
对于变量 struct sdshdr##T *sh
大家可能会有纳闷,其实就是相当于在调用宏的中央定义了一个 sh
变量。咱们用一个例子模仿一下:
#include <stdio.h>
#define INTPTR_VAR int* p = NULL;
int main()
{
int a = 10;
INTPTR_VAR;
p = &a;
printf("%d\n", *p);
return 0;
}
后果:输入 10
依据宏的特点,在预处理时,宏就间接被表达式替换了,上述例子就等价于:
#include <stdio.h>
int main()
{
int a = 10;
int* p = NULL; // INTPTR_VAR
p = &a;
printf("%d\n", *p);
return 0;
}
至此,咱们就明确了 SDS_HDR_VAR
是定义一个指向 保留字符串 s 的构造体指针。
这样咱们就好了解 SDS_HDR(T, S)
的作用了,即返回一个 保留字符串 s 的构造体地址
留神两者的差异:SDS_HDR_VAR
是定义变量保留构造体地址,SDS_HDR
是返回构造体地址。
对于 sds 属性的函数(sds.h)
sdslen
static inline size_t sdslen(const sds s) {unsigned char flags = s[-1];
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:
return SDS_HDR(16,s)->len;
case SDS_TYPE_32:
return SDS_HDR(32,s)->len;
case SDS_TYPE_64:
return SDS_HDR(64,s)->len;
}
return 0;
}
顾名思义,该函数是用来获取字符串长度的。
参数 sds
:
typedef char * sds;
这里咱们就晓得了下面讲的 SDS_HDR
的作用了,通过字符串指针 s 来获取蕴含它的构造体地址,进一步拜访其中的成员。
s[-1] 即示意 sdshdr 构造中的成员 flags,通过 flags 咱们能够判断 sdshdr 的类型。
switch 的表达式 flags & SDS_TYPE_MASK
,SDS_TYPE_MASK 的定义:#define SDS_TYPE_MASK 7
,因为这里采纳的是按位与操作,所以咱们把 7 转换成二进制:000…0111,所以 SDS_TYPE_MASK
的作用就是取 flags 的低 3 位。
case 表达式的变量就是对应类型的值。
咱们看进入 SDS_TYPE_5
时,返回的是一个宏 SDS_TYPE_5_LEN
的后果,SDS_TYPE_5_LEN
的定义:#define SDS_TYPE_5_LEN(f) ((f) >> SDS_TYPE_BITS)
,SDS_TYPE_BITS
的定义:#define SDS_TYPE_BITS 3
,由此咱们能够看到,SDS_TYPE_5_LEN
是将 flags 右移三位,即返回 0。因为 sdshdr5 是不应用的,所以对其长度也返回 0.
其余 case 分支就是返回它们的 len
变量。
sdsavail
static inline size_t sdsavail(const sds s) {unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5: {return 0;}
case SDS_TYPE_8: {SDS_HDR_VAR(8,s);
return sh->alloc - sh->len;
}
case SDS_TYPE_16: {SDS_HDR_VAR(16,s);
return sh->alloc - sh->len;
}
case SDS_TYPE_32: {SDS_HDR_VAR(32,s);
return sh->alloc - sh->len;
}
case SDS_TYPE_64: {SDS_HDR_VAR(64,s);
return sh->alloc - sh->len;
}
}
return 0;
}
返回 sds 的可用空间。sds 与传统的 C 字符串不同,C 字符串以‘\0’结尾,而 sds 则是为字符串多调配了一段空间,缩小之后增容所带来的开销。
sdssetlen
static inline void sdssetlen(sds s, size_t newlen) {unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
{unsigned char *fp = ((unsigned char*)s)-1;
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
}
break;
case SDS_TYPE_8:
SDS_HDR(8,s)->len = newlen;
break;
case SDS_TYPE_16:
SDS_HDR(16,s)->len = newlen;
break;
case SDS_TYPE_32:
SDS_HDR(32,s)->len = newlen;
break;
case SDS_TYPE_64:
SDS_HDR(64,s)->len = newlen;
break;
}
}
批改 sds 的无效长度。
sdsinclen
static inline void sdsinclen(sds s, size_t inc) {unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
{unsigned char *fp = ((unsigned char*)s)-1;
unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc;
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
}
break;
case SDS_TYPE_8:
SDS_HDR(8,s)->len += inc;
break;
case SDS_TYPE_16:
SDS_HDR(16,s)->len += inc;
break;
case SDS_TYPE_32:
SDS_HDR(32,s)->len += inc;
break;
case SDS_TYPE_64:
SDS_HDR(64,s)->len += inc;
break;
}
}
减少 sds 的无效长度。
sdsalloc
static inline size_t sdsalloc(const sds s) {unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
return SDS_TYPE_5_LEN(flags);
case SDS_TYPE_8:
return SDS_HDR(8,s)->alloc;
case SDS_TYPE_16:
return SDS_HDR(16,s)->alloc;
case SDS_TYPE_32:
return SDS_HDR(32,s)->alloc;
case SDS_TYPE_64:
return SDS_HDR(64,s)->alloc;
}
return 0;
}
返回 sds 的已调配空间大小。也能够通过 sdsavail() + sdslen()
来获取
sdssetalloc
static inline void sdssetalloc(sds s, size_t newlen) {unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
/* Nothing to do, this type has no total allocation info. */
break;
case SDS_TYPE_8:
SDS_HDR(8,s)->alloc = newlen;
break;
case SDS_TYPE_16:
SDS_HDR(16,s)->alloc = newlen;
break;
case SDS_TYPE_32:
SDS_HDR(32,s)->alloc = newlen;
break;
case SDS_TYPE_64:
SDS_HDR(64,s)->alloc = newlen;
break;
}
}
设置 sds 的已调配空间大小
创立 / 批改 / 销毁 sds(sds.c)
要创立一个 sds 对象,首先要确认其 sds 构造属于哪一种,所以咱们要依据字符串长度来抉择 sdshdr。
上面是依据字符串长度来确认 sds 类型的几个相干函数:
sdsReqType
static inline char sdsReqType(size_t string_size) {if (string_size < 1<<5)
return SDS_TYPE_5;
if (string_size < 1<<8)
return SDS_TYPE_8;
if (string_size < 1<<16)
return SDS_TYPE_16;
#if (LONG_MAX == LLONG_MAX) // 64 位
if (string_size < 1ll<<32)
return SDS_TYPE_32;
return SDS_TYPE_64;
#else // 32 位
return SDS_TYPE_32;
#endif
}
依据字符串长度来确定 sds 的类型。
咱们看其中有一个 #if
的预处理符号,在不同机器下 (32 位与 64 位) 返回不同后果。long 和 size_t 在 32 位下是 4 字节,64 位下是 8 字节
sdsHdrSize
static inline int sdsHdrSize(char type) {switch(type&SDS_TYPE_MASK) {
case SDS_TYPE_5:
return sizeof(struct sdshdr5);
case SDS_TYPE_8:
return sizeof(struct sdshdr8);
case SDS_TYPE_16:
return sizeof(struct sdshdr16);
case SDS_TYPE_32:
return sizeof(struct sdshdr32);
case SDS_TYPE_64:
return sizeof(struct sdshdr64);
}
return 0;
}
依据 sds 的类型返回对应 sdshdr 的大小
sdsTypeMaxSize
static inline size_t sdsTypeMaxSize(char type) {if (type == SDS_TYPE_5)
return (1<<5) - 1;
if (type == SDS_TYPE_8)
return (1<<8) - 1;
if (type == SDS_TYPE_16)
return (1<<16) - 1;
#if (LONG_MAX == LLONG_MAX)
if (type == SDS_TYPE_32)
return (1ll<<32) - 1;
#endif
return -1; /* this is equivalent to the max SDS_TYPE_64 or SDS_TYPE_32 */
}
依据 sds 的类型返回其类型最大值
理解这些接口后,咱们接下来介绍创立一个 sdshdr 构造体的函数:
sdsnewlen
sds sdsnewlen(const void *init, size_t initlen) {return _sdsnewlen(init, initlen, 0);
}
sdsnewlen
是用来创立一个长度为 initlen 的 sds,并应用 init 指向的字符串来初始化 sds,如果 init 为 NULL,则将 sds 全副初始化为‘\0’。能够看到它是调用了 _sdsnewlen
接口,该接口源码如下:
_sdsnewlen
sds _sdsnewlen(const void *init, size_t initlen, int trymalloc) {
void *sh;
sds s;
char type = sdsReqType(initlen);
/* Empty strings are usually created in order to append. Use type 8
* since type 5 is not good at this. */
if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
int hdrlen = sdsHdrSize(type);
unsigned char *fp; /* flags pointer. */
size_t usable;
assert(initlen + hdrlen + 1 > initlen); /* Catch size_t overflow */
sh = trymalloc?
s_trymalloc_usable(hdrlen+initlen+1, &usable) :
s_malloc_usable(hdrlen+initlen+1, &usable);
if (sh == NULL) return NULL;
if (init==SDS_NOINIT)
init = NULL;
else if (!init)
memset(sh, 0, hdrlen+initlen+1);
s = (char*)sh+hdrlen;
fp = ((unsigned char*)s)-1;
usable = usable-hdrlen-1;
if (usable > sdsTypeMaxSize(type))
usable = sdsTypeMaxSize(type);
switch(type) {
case SDS_TYPE_5: {*fp = type | (initlen << SDS_TYPE_BITS);
break;
}
case SDS_TYPE_8: {SDS_HDR_VAR(8,s);
sh->len = initlen;
sh->alloc = usable;
*fp = type;
break;
}
case SDS_TYPE_16: {SDS_HDR_VAR(16,s);
sh->len = initlen;
sh->alloc = usable;
*fp = type;
break;
}
case SDS_TYPE_32: {SDS_HDR_VAR(32,s);
sh->len = initlen;
sh->alloc = usable;
*fp = type;
break;
}
case SDS_TYPE_64: {SDS_HDR_VAR(64,s);
sh->len = initlen;
sh->alloc = usable;
*fp = type;
break;
}
}
if (initlen && init)
memcpy(s, init, initlen);
s[initlen] = '\0';
return s;
}
trymalloc 参数示意是调用 s_trymalloc_usable
还是 s_malloc_usable
,对于两个函数咱们不进行太多钻研。
当计算失去 initlen 对应的 sds 类型后,如果是 SDS_TYPE_5
,则将类型转变为 SDS_TYPE_8
,随后计算对应的 sdshdr 大小 hdrlen,申请的空间为 hdrlen + initlen+ 1,hdrlen 是 sdshdr 构造体的大小,initlen 是字符串的长度,1 是字符串结尾‘\0’的大小。
随后是对 sdshdr 成员的赋值。在除了 case SDS_TYPE_5 的其余 case 分支中,都有用到宏 SDS_HDR_VAR
,下面咱们提到了它创立一个指向蕴含 s 的构造体指针,前面的 sh 不是函数一开始创立的 sh,而是宏创立的 sh。
其余创立 sds 的函数:
/* Create an empty (zero length) sds string. Even in this case the string
* always has an implicit null term. */
sds sdsempty(void) {return sdsnewlen("",0);
}
/* Create a new sds string starting from a null terminated C string. */
sds sdsnew(const char *init) {size_t initlen = (init == NULL) ? 0 : strlen(init);
return sdsnewlen(init, initlen);
}
/* Duplicate an sds string. */
sds sdsdup(const sds s) {return sdsnewlen(s, sdslen(s));
}
sdsempty 是创立一个空的 sds 对象。
sdsnew 则是依据 init 指向的内容来创立 sds 对象。如果 init 为空,则 initlen 为 0,如果不为空,则依照 init 指向的字符数组创立 sds 对象。
sdsup 则是依据已存在的 sds 对象来创立一个 sds 对象
销毁 sds 的函数
/* Free an sds string. No operation is performed if 's' is NULL. */
void sdsfree(sds s) {if (s == NULL) return;
s_free((char*)s-sdsHdrSize(s[-1]));
}
(char*)s-sdsHdrSize(s[-1])
相似于 SDS_HDR
操作,即返回一个指向 s 所在构造体对象的指针
批改容量
增容
/* Enlarge the free space at the end of the sds string more than needed,
* This is useful to avoid repeated re-allocations when repeatedly appending to the sds. */
sds sdsMakeRoomFor(sds s, size_t addlen) {return _sdsMakeRoomFor(s, addlen, 1);
}
/* Unlike sdsMakeRoomFor(), this one just grows to the necessary size. */
sds sdsMakeRoomForNonGreedy(sds s, size_t addlen) {return _sdsMakeRoomFor(s, addlen, 0);
sdsMakeRoomFor
和 sdsMakeRoomForNonGreedy
为 s 减少 adlen 个字节的长度,能够看到这两个函数都是调用了 _sdsMakeRoomFor
接口,只不过在参数传递上,第三个参数有所不同。增容时如果 sds 中的 buf 空间曾经足够包容增加 addlen 之后的长度,则什么都不做;如果空间有余,则须要增容。第三个参数如果是 1,则增容时会多开一些空间,以便将来的增容有空间可用; 如果是 0,则只会开拓加上 addlen 之后的空间大小,不会多预留空间。
_sdsMakeRoomFor
sds _sdsMakeRoomFor(sds s, size_t addlen, int greedy) {
void *sh, *newsh;
size_t avail = sdsavail(s);
size_t len, newlen, reqlen;
char type, oldtype = s[-1] & SDS_TYPE_MASK;
int hdrlen;
size_t usable;
/* Return ASAP if there is enough space left. */
if (avail >= addlen) return s;
len = sdslen(s);
sh = (char*)s-sdsHdrSize(oldtype);
reqlen = newlen = (len+addlen);
assert(newlen > len); /* Catch size_t overflow */
if (greedy == 1) {if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
}
type = sdsReqType(newlen);
/* Don't use type 5: the user is appending to the string and type 5 is
* not able to remember empty space, so sdsMakeRoomFor() must be called
* at every appending operation. */
if (type == SDS_TYPE_5) type = SDS_TYPE_8;
hdrlen = sdsHdrSize(type);
assert(hdrlen + newlen + 1 > reqlen); /* Catch size_t overflow */
if (oldtype==type) {newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);
if (newsh == NULL) return NULL;
s = (char*)newsh+hdrlen;
} else {
/* Since the header size changes, need to move the string forward,
* and can't use realloc */
newsh = s_malloc_usable(hdrlen+newlen+1, &usable);
if (newsh == NULL) return NULL;
memcpy((char*)newsh+hdrlen, s, len+1);
s_free(sh);
s = (char*)newsh+hdrlen;
s[-1] = type;
sdssetlen(s, len);
}
usable = usable-hdrlen-1;
if (usable > sdsTypeMaxSize(type))
usable = sdsTypeMaxSize(type);
sdssetalloc(s, usable);
return s;
}
在这里咱们能够看到《Redis 设计与实现》提到的 sds 预留空间策略 (greedy == 1) 的两种状况:
- 当
newlen < SDS_MAX_PREALLOC
时(SDS_MAX_PREALLOC
的定义:#define SDS_MAX_PREALLOC (1024*1024)
),即当新长度小于 1MB 时,预留空间长度为 2 * newlen - 当
newlen >= SDS_MAX_PREALLOC
,预留空间长度为 1MB
增容后的 sds 可能长度超过原 sdshdr 的最大长度,此时就要为 sds 调配一个新的 sdshdr。
去除为 sds 预留的空间
sds sdsRemoveFreeSpace(sds s) {
void *sh, *newsh;
char type, oldtype = s[-1] & SDS_TYPE_MASK;
int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
size_t len = sdslen(s);
size_t avail = sdsavail(s);
sh = (char*)s-oldhdrlen;
/* Return ASAP if there is no space left. */
if (avail == 0) return s;
/* Check what would be the minimum SDS header that is just good enough to
* fit this string. */
type = sdsReqType(len);
hdrlen = sdsHdrSize(type);
/* If the type is the same, or at least a large enough type is still
* required, we just realloc(), letting the allocator to do the copy
* only if really needed. Otherwise if the change is huge, we manually
* reallocate the string to use the different header type. */
if (oldtype==type || type > SDS_TYPE_8) {newsh = s_realloc(sh, oldhdrlen+len+1);
if (newsh == NULL) return NULL;
s = (char*)newsh+oldhdrlen;
} else {newsh = s_malloc(hdrlen+len+1);
if (newsh == NULL) return NULL;
memcpy((char*)newsh+hdrlen, s, len+1);
s_free(sh);
s = (char*)newsh+hdrlen;
s[-1] = type;
sdssetlen(s, len);
}
sdssetalloc(s, len);
return s;
}
调用该函数后,不可再应用传入的 s,而应该应用返回的 s,其余援用传入的 s 也应该批改指向,因为原来空间会被开释。
只有产生微小的空间缩短时,sdshdr 才会批改
重新分配 sds 的空间
sds sdsResize(sds s, size_t size) {
void *sh, *newsh;
char type, oldtype = s[-1] & SDS_TYPE_MASK;
int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
size_t len = sdslen(s);
sh = (char*)s-oldhdrlen;
/* Return ASAP if the size is already good. */
if (sdsalloc(s) == size) return s;
/* Truncate len if needed. */
if (size < len) len = size;
/* Check what would be the minimum SDS header that is just good enough to
* fit this string. */
type = sdsReqType(size);
/* Don't use type 5, it is not good for strings that are resized. */
if (type == SDS_TYPE_5) type = SDS_TYPE_8;
hdrlen = sdsHdrSize(type);
/* If the type is the same, or can hold the size in it with low overhead
* (larger than SDS_TYPE_8), we just realloc(), letting the allocator
* to do the copy only if really needed. Otherwise if the change is
* huge, we manually reallocate the string to use the different header
* type. */
if (oldtype==type || (type < oldtype && type > SDS_TYPE_8)) {newsh = s_realloc(sh, oldhdrlen+size+1);
if (newsh == NULL) return NULL;
s = (char*)newsh+oldhdrlen;
} else {newsh = s_malloc(hdrlen+size+1);
if (newsh == NULL) return NULL;
memcpy((char*)newsh+hdrlen, s, len);
s_free(sh);
s = (char*)newsh+hdrlen;
s[-1] = type;
}
s[len] = 0;
sdssetlen(s, len);
sdssetalloc(s, size);
return s;
}
如果 size 小于以后曾经应用的字符串长度,则字符串会被截断。
sds 的操作 (sds.c)
这里只挑一些笔者认为具备代表的函数进行讲述。
拼接
/* Append the specified null terminated C string to the sds string 's'.
*
* After the call, the passed sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */
sds sdscat(sds s, const char *t) {return sdscatlen(s, t, strlen(t));
}
/* Append the specified sds 't' to the existing sds 's'.
*
* After the call, the modified sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */
sds sdscatsds(sds s, const sds t) {return sdscatlen(s, t, sdslen(t));
}
sdscat
是将一个 C 字符串拼接到 sds 之后,调用了 sdscatlen
来实现拼接
sdscatsds
是将 sds 拼接到另一个 sds 之后,同样也调用了 sdscatlen
来实现
这两者调用后,都不能再应用原来的 sds,因为在 sdscatlen
中进行了扩容,所以原空间销毁,所有援用原 sds 的局部都要替换成返回的 sds
sdscatlen
/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the
* end of the specified sds string 's'.
*
* After the call, the passed sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */
sds sdscatlen(sds s, const void *t, size_t len) {size_t curlen = sdslen(s);
s = sdsMakeRoomFor(s,len);
if (s == NULL) return NULL;
memcpy(s+curlen, t, len);
sdssetlen(s, curlen+len);
s[curlen+len] = '\0';
return s;
}
在 sdscatlen
中,先为 s 增了 len
个长度,再在 s 前面拼接 t。
拷贝
/* Destructively modify the sds string 's' to hold the specified binary
* safe string pointed by 't' of length 'len' bytes. */
sds sdscpylen(sds s, const char *t, size_t len) {if (sdsalloc(s) < len) {s = sdsMakeRoomFor(s,len-sdslen(s));
if (s == NULL) return NULL;
}
memcpy(s, t, len);
s[len] = '\0';
sdssetlen(s, len);
return s;
}
/* Like sdscpylen() but 't' must be a null-terminated string so that the length
* of the string is obtained with strlen(). */
sds sdscpy(sds s, const char *t) {return sdscpylen(s, t, strlen(t));
}
sdscpylen
将 s 齐全批改,即从头开始拷贝 t 的内容。
sdscpy
复用了 sdscpylen
函数,要求参数 t 是以 NULL 结尾的,因为它要通过 strlen
来计算长度