sds结构
struct sdshdr {
/** buf 中已占用空间的长度*/
int len;
/** buf 中剩余可用空间的长度*/
int free;
/** 数据空间*/
/** 柔性数组*/
char buf[];
};
获取字符串长度复杂度为O(1)
在sds的结构中,保存了sds的占用空间的长度,可以使用sdslen进行读取,代码实现如下:
static inline size_t sdslen(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->len;
}
二进制安全
二进制安全:比如说在c语言中,strlen计算长度,是依赖于特殊的字符'\0'来判断是否到达字符串的最末尾,所以对于"aaa\0bbb"来说,它就不是二进制安全的
sds的二进制安全,它不仅仅根据"\0"来判断是否到达字符串的末尾,还根据len的长度来进行计算
杜绝缓冲区溢出
缓冲区溢出,指的是在修改字符串的时候,没有对该字符串分配足够的空间
sds在对字符串进行修改的时候,都会使用(sdsMakeRoomFor)判断是否有足够的空间
sds sdscatlen(sds s, const void *t, size_t len) {
struct sdshdr *sh;
// 原有字符串长度
size_t curlen = sdslen(s);
// 扩展 sds 空间
// T = O(N)
s = sdsMakeRoomFor(s, len);
// 内存不足?直接返回
if (s == NULL) {
return NULL;
}
// 复制 t 中的内容到字符串后部
// T = O(N)
sh = (void*) (s - (sizeof(struct sdshdr)));
memcpy(s + curlen, t, len);
// 更新属性
sh->len = curlen + len;
sh->free = sh->free - len;
// 添加新结尾符号
s[curlen + len] = '\0';
// 返回新 sds
return s;
}
动态扩展,空间预分配
对字符串进行修改的时候,会判断sds的剩余空间,如果大小不足够会重新进行分配,具体分配逻辑如下:
1、如果新长度小于SDS_MAX_PREALLOC定义的最大空间,就分配两倍所需空间;
2、否则在目前的长度上再增加SDS_MAX_PREALLOC定义的最大空间
代码实现如下:
/*
* 对 sds 中 buf 的长度进行扩展,确保在函数执行之后,
* buf 至少会有 addlen + 1 长度的空余空间
* (额外的 1 字节是为 \0 准备的)
*
* 返回值
* sds :扩展成功返回扩展后的 sds
* 扩展失败返回 NULL
*
* 复杂度
* T = O(N)
*/
sds sdsMakeRoomFor(sds s, size_t addlen) {
struct sdshdr *sh, *newsh;
// 获取 s 目前的空余空间长度
size_t free = sdsavail(s);
size_t len, newlen;
// s 目前的空余空间已经足够,无须再进行扩展,直接返回
if (free >= addlen) {
return s;
}
// 获取 s 目前已占用空间的长度
len = sdslen(s);
sh = (void*)(s - (sizeof(struct sdshdr)));
// s 最少需要的长度
newlen = (len + addlen);
// 根据新长度,为 s 分配新空间所需的大小
// 如果当前占用内存小于1M那么就分配两倍于当前长度的内存
// 如果当前占用内存大于等于1M那么就分配当前长度+1M的内存
// 内存增长策略*****
if (newlen < SDS_MAX_PREALLOC) {
// 如果新长度小于 SDS_MAX_PREALLOC
// 那么为它分配两倍于所需长度的空间
newlen *= 2;
} else {
// 否则,分配长度为目前长度加上 SDS_MAX_PREALLOC
newlen += SDS_MAX_PREALLOC;
}
// T = O(N)
newsh = zrealloc(sh, sizeof(struct sdshdr) + newlen + 1);
// 内存不足,分配失败,返回
if (newsh == NULL) {
return NULL;
}
// 更新 sds 的空余长度
newsh->free = newlen - len;
// 返回 sds
return newsh->buf;
}
惰性释放,减少内存分配次数
对sds字符串进行缩减操作的时候,不会对空间进行释放,仅仅只是改变len属性和free属性的值
void sdsclear(sds s) {
// 取出 sdshdr
struct sdshdr *sh = (void*) (s - (sizeof(struct sdshdr)));
// 重新计算属性
sh->free += sh->len;
sh->len = 0;
// 将结束符放到最前面(相当于惰性地删除 buf 中的内容)
sh->buf[0] = '\0';
}
long数值转字符串
这个函数比较有意思,他将long long类型的数值转换成string类型
1、将数值按照10取模,取模后的值保存,在除10(相当于是对当前位的上一位进行获取 12345第一次取模的值是5,除10变成1234.5取模的值变成4)(注意101.1与10取模是1不是1.1)
2、将保存的字符串再进行翻转
比如:
12345 取模后得到的值依次是5,4,3,2,1 连接成最后的字符串是54321
再进行倒转以后变成12345
int sdsll2str(char *s, long long value) {
char *p, aux;
unsigned long long v;
size_t l;
/* Generate the string representation, this method produces
* an reversed string. */
v = (value < 0) ? -value : value;
p = s;
do {
*p++ = '0' + (v%10);
v /= 10;
} while(v);
if (value < 0) *p++ = '-';
/* Compute length and add null term. */
l = p - s;
*p = '\0';
/* Reverse the string. */
p--;
while(s < p) {
aux = *s;
*s = *p;
*p = aux;
s++;
p--;
}
return l;
}
为什么要'0'+单个数字?
参考ascii表,字符串1对应的ascii码是49,'0'对应的ancii值是48,比如说数字3想存储到字符串中表示也为'3',那么它对应的ascii就是3+48也就是3+'0'