Simple Dynamic Strings 动态字符串 是Redis 的基本数据类型之一,也是最常见的数据类型。兼容了C语言标准字符串处理函数,在此基础上保证了二进制安全。
目录
1.常用命令
127.0.0.1:6379> get str
"helloWorld"
127.0.0.1:6379> TYPE str
string
127.0.0.1:6379> STRLEN str
(integer) 10
127.0.0.1:6379>
2.源码
1.源码文件在redis/src/sds.h. sds.c
-
//创建sds 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. */ 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; /* 表示字符串已使用的长度,buf 长度 */ uint8_t alloc; /* 表示字符串的容量 */ unsigned char flags; /* 低3位存储类型,高5位存储长度 */ 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[]; };
使用 __attribute__ ((__packed__)) 关键字,告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行紧凑对齐,是GCC编译器特有语法。
- sds 字符串根据字符串的长度,区分了5种结构
函数名 | 类型 | 存取最大字符串长度 |
sdshdr5 | SDS_TYPE_5 | 32(2^5) |
sdshdr8 | SDS_TYPE_8 | 0xff(2^8-1) |
sdshdr16 | SDS_TYPE_16 | 0xffff(2^16-1) |
sdshdr32 | SDS_TYPE_32 | 0xffffffff(2^32-1) |
sdshdr64 | SDS_TYPE_64 | 2^64 -1 |
1.初始化SDS
//创建初始化sds
sds sdsnewlen(const void *init, size_t initlen) {
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. */
sh = s_malloc(hdrlen+initlen+1);
if (init==SDS_NOINIT)
init = NULL;
else if (!init)
memset(sh, 0, hdrlen+initlen+1);
if (sh == NULL) return NULL;
s = (char*)sh+hdrlen;
fp = ((unsigned char*)s)-1;
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 = 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);
s[initlen] = '\0';
return s;
}
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;
}
-
1. 根据初始化长度获取SDS 的结构类型,如果长度为initlen == 0 ,直接设置SDS 类型为SDS_TYPE_8;
-
2.分配内存, 内存空间长度 = hdrlen+initlen+1 (头部长度+ buf 的长度 + 字符串最后空格’\0’)
-
3.根据SDS 类型初始化sds 头部,长度,标记,容量
-
4.复制数据memcpy
2.空间增加:字符串拼接
/* 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;
}
/* Append the specified null termianted 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 与 sdscatsds 都可以作为sds字符串的拼接,拼接会根据类型检查是否需要扩容,如果需要扩容需要预分配空间,在函数 sdsMakeRoomFor 会有详细体现
/* 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;
//获取目前s的可用空间
size_t avail = sdsavail(s);
size_t len, newlen;
char type, oldtype = s[-1] & SDS_TYPE_MASK;
int hdrlen;
//如果可用空间大于等于新增空间则直接return
if (avail >= addlen) return s;
len = sdslen(s);
sh = (char*)s-sdsHdrSize(oldtype);
newlen = (len+addlen);
// 如果newlen 长度小于 SDS_MAX_PREALLOC (1024*1024)长度则分配2倍所需空间长度
// 否则 分配所需空间+ SDS_MAX_PREALLOC 长度
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
type = sdsReqType(newlen);
// 不再使用SDS_TYPE_5 类型,如果类型为SDS_TYPE_5 改用SDS_TYPE_8 类型
if (type == SDS_TYPE_5) type = SDS_TYPE_8;
hdrlen = sdsHdrSize(type);
if (oldtype==type) {
//sds 类型无变化,重新分配内存即可
newsh = s_realloc(sh, hdrlen+newlen+1);
if (newsh == NULL) return NULL;
s = (char*)newsh+hdrlen;
} else {
/ *类型改变,由于标头大小发生变化,因此需要将字符串向前移动*,并且不能使用realloc */
newsh = s_malloc(hdrlen+newlen+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, newlen);
return s;
}
3.空间释放
/ *重新分配sds字符串,使其末尾没有可用空间。 字符串保持不变,但是接下来的串联操作将需要重新分配。 *在调用之后,传递的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;
//可用空间长度为0 直接返回
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 (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;
}