sds字符串

Redis 只会使用 C 字符串作为字面量, 在大多数情况下, Redis 使用 SDS (Simple Dynamic String,简单动态字符串)作为字符串表示。 比起 C 字符串, SDS 具有以下优点:

  1. 常数复杂度获取字符串长度。
  2. 杜绝缓冲区溢出。
  3. 减少修改字符串长度时所需的内存重分配次数。
  4. 二进制安全。
  5. 兼容部分 C 字符串函数。

根据传统, C 语言使用长度为N+1的字符数组来表示长度为 N 的字符串,并且字符数组的最后一个元素总是空字符 '\0' 。

C 语言使用的这种简单的字符串表示方式, 并不能满足 Redis 对字符串在安全性、效率、以及功能方面的要求, 本节接下来的内容将详细对比 C 字符串和 SDS 之间的区别, 并说明 SDS 比 C 字符串更适用于 Redis 的原因。

SDS又叫简单动态字符串,在Redis中默认使用SDS来表示字符串。比如在Redis中的键值对中的键一般都是使用SDS来实现。首先需要说明的是在Redis中,字符串不是用传统的字符串来实现,而是Redis自己构建了一个结构来表示字符串。优点如下:

1、O(1)时间内获取字符串长度。常数复杂度获取字符串长度(依据其结构特性,只需要访问其结构体成员len既可获得字符串长度)

2、杜绝缓冲区溢出,另外SDS还提供的一些API操作,是二进制安全的(也就是不会因为空格等特殊字符而中断字符串)、不会溢出(API操作会检查其长度)

3、减少了修改字符串时带来的内存重分配次数。

  • 对于增长字符串其采用的策略是检查修改之后的长度大小,如果小于1024*1024,则分配2倍的修改后的长度+1
  • 对于减少的字符串其并不立即释放空间,而是回归到alloc中去。(自动查阅redis内存分配与释放策略)
typedef char *sds;//底层用于存储字符串的数据结构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. */
struct __attribute__ ((__packed__)) sdshdr5
//低三位保存type,高5位保存长度
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

从代码中可以看出,SDS表示的字符串是有SDS header和char*指针组成,而SDS的头部主要由四部分组成:

  • len:SDS字符串已使用的空间。
  • alloc:申请的空间,减去len就是未使用的空间,初始时和len一致。
  • flag:只使用了低三位表示类型,细化了SDS的分类,根据字符串的长度的不同选择不同的sds结构体,而结构体的主要区别是len和alloc的类型,这样做可以节省一部分空间大小,毕竟在redis字符串非常多,进一步的可以节省空间。
  • buf: 用了C的特性表示不定长字符串。

除了sdshdr5之外,其他结构都是相似的。我们先看看sdshdr,它只有flags和buf成员,其中flag空间被C充分利用,其第三位保存了SDS字符串的类型.

#define SDS_TYPE_MASK 7   // 类型掩码
#define SDS_TYPE_BITS 3
#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)))) // 获取header头指针
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) // 获取sdshdr5的长度,低三位保存了type
/*sds结构一共有五种Header定义,其目的是为了满足不同长度的字符串可以使用不同大小的Header,从而节省内存。 
Header部分主要包含以下几个部分:len、alloc、flags其中
len:表示字符串真正的长度,不包含空终止字和alloc以及flag
alloc:表示字符串的最大容量,不包含Header和最后的空终止字符和flag
flag:只用了3位表示sds的type 就是表示header的类型*/
// 五种header类型,flags取值为0~4
#define
#define
#define
#define
#define

sdsnewlen函数

sds创建函数

/* Create a new sds string with the content specified by the 'init' pointer
* and 'initlen'.
* If NULL is used for 'init' the string is initialized with zero bytes.
*
* The string is always null-termined (all the sds strings are, always) so
* even if you create an sds string with:
*
* mystring = sdsnewlen("abc",3);
*
* You can print the string with printf() as there is an implicit \0 at the
* end of the string. However the string is binary safe and can contain
* \0 characters in the middle, as the length is stored in the sds header. */
sds sdsnewlen(const void *init, size_t initlen) {
void *sh;
sds s;

// 根据initlen 求sds使用的结构体类型
char type = sdsReqType(initlen);

//下面是我在测试的时候添加的打印字符串类型代码,源码中没有
int type_t = type;
printf("sds.c------->sdsnewlen: the sds type is: [ %d ]\n", type_t);
/* Empty strings are usually created in order to append. Use type 8
* since type 5 is not good at this. */

//type5 不再使用 而是直接使用type8
if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;

//sds使用的结构体的大小
int hdrlen = sdsHdrSize(type);
unsigned char *fp; /* flags pointer. */ //flag的第三位表示sds使用的结构体

//分配内层 大小是sds结构体的大小: hdrlen + buf的大小initlen + '\0'
sh = s_malloc(hdrlen+initlen+1);
//init为null的时候 直接初始化sh内容
if (!init)
memset(sh, 0, hdrlen+initlen+1);
//sh 分配失败的情况直接返回null
if (sh == NULL) return NULL;

// s为数据部分的起始指针 指向buf地址指针
s = (char*)sh+hdrlen;
//fp指向flag flag的低三位表示sds使用的结构体类型
fp = ((unsigned char*)s)-1;

//根据sds使用的结构体类型 给结构体中的成员赋值
switch(type) {
case SDS_TYPE_5: {
// initlen << SDS_TYPE_BITS 把initlen的值保存到flag的高5位中去 低三位保存type的值
*fp = type | (initlen << SDS_TYPE_BITS);
break;
}
case SDS_TYPE_8: {
SDS_HDR_VAR(8,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_16: {
SDS_HDR_VAR(16,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_32: {
SDS_HDR_VAR(32,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_64: {
SDS_HDR_VAR(64,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
}
if (initlen && init)
//拷贝数据部分
memcpy(s, init, initlen);

// 与C字符串兼容
s[initlen] = '\0';

// 返回创建的sds字符串指针
return

sdsMakeRoomFor函数

函数原型:sds sdsMakeRoomFor(sds s, size_t addlen)

  • 说明:实现扩充已有sds的可用空间为指定的大小,扩充规则是:当addlen的长度小于1024*1024时,则申请的空间是2*(addlen+len),否则扩充为1024*1024大小。
  • 返回值:扩充后的sds对象
/* Enlarge the free space at the end of the sds string so that the caller
* is sure that after calling this function can overwrite up to addlen
* bytes after the end of the string, plus one more byte for nul term.
*
* Note: this does not change the *length* of the sds string as returned
* by sdslen(), but only the free buffer space we have. */
sds sdsMakeRoomFor(sds s, size_t addlen) {
void *sh, *newsh;
size_t avail = sdsavail(s); //返回剩余可用的空间。
size_t len, newlen;
char type, oldtype = s[-1] & SDS_TYPE_MASK; //获取type
int hdrlen;

/* Return ASAP if there is enough space left. */
if (avail >= addlen) return s; //如果可用空间大于addlen直接返回旧的字符串

len = sdslen(s); //求sds的长度
sh = (char*)s-sdsHdrSize(oldtype);
newlen = (len+addlen);
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;

//根据newlen调整sds的type
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;

//根据type获取sds使用的结构体的长度
hdrlen = sdsHdrSize(type);

//若类型和原有类型一样,则采用realloc分配空间,否则重新分配采用malloc函数分配空间
if (oldtype==type) {
newsh = s_realloc(sh, hdrlen+newlen+1);
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(hdrlen+newlen+1);
if (newsh == NULL) return NULL;
memcpy((char*)newsh+hdrlen, s, len+1); //把s中的字符串拷贝到newsh指向的buf中去
s_free(sh);//释放旧的sh空间
s = (char*)newsh+hdrlen; //s指向重新分配的字符串
s[-1] = type; //指定newsds的type
sdssetlen(s, len); //设置新的字符串的长度
}
sdssetalloc(s, newlen); //设置新的newsh的分配的空间长度
return

**sdscatlen **:sds提供了字符串的连接函数,用来连接两个字符串

//sds字符串连接
sds sdscatlen(sds s, const void *t, size_t{
size_t curlen = sdslen(s);

//扩展s的空间
s = sdsMakeRoomFor(s,len);
if (s == NULL) return NULL;

// 连接新字符串
memcpy(s+curlen, t, len);

// 设定连接后字符串长度
sdssetlen(s, curlen+len);
s[curlen+len] = '\0';
return
sds sdsempty(void); // 清空sds
sds sdsdup(const; // 复制字符串
sds sdsgrowzero(sds s, size_t; // 扩展字符串到指定长度
sds sdscpylen(sds s, const char *t, size_t; // 字符串的复制
sds sdscpy(sds s, const char; // 字符串的复制
sds sdscatfmt(sds s, char const; //字符串格式化输出
sds sdstrim(sds s, const char; //字符串缩减
void sdsrange(sds s, int start, int; //字符串截取函数
void sdsupdatelen(sds s); //更新字符串最新的长度
void sdsclear(sds s); //字符串清空操作
void sdstolower(sds s); //sds字符转小写表示
void sdstoupper(sds s); //sds字符统一转大写
sds sdsjoin(char **argv, int argc, char; //以分隔符连接字符串子数组构成新的字符串

sdsull2str

#include <iostream>
using namespace std;
int sdsll2str(char *s, long long{
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--;
}

// std::cout << s << std::endl;
return l;
}


int main(){

char str[10]={0};
sdsll2str(str, 1234567);
std::cout << str << std::endl;
}