Redis的SDS(Simple Dynamic String)是Redis中使用最多的基础数据结构之一。它具有以下特色:
动态扩展内存:SDS可以动态地扩展内存,允许字符串的内容进行修改和追加。这与其他语言中的字符串类型不同,它们通常分为可变和不可变两种类型。
二进制安全:SDS可以存储任意二进制数据,而不仅仅是可打印字符。它是二进制安全的,意味着它可以存储任意字节的数据,而不会因为特殊字符而导致数据损坏。
与传统C语言字符串兼容:SDS与传统的C语言字符串类型兼容,可以在需要传入C语言字符串的地方使用SDS。它的类型定义与C语言字符串相同,都是char*类型。
SDS的数据结构定义如下:
ctypedef 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; /* 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[];
};
其中sdshdr5已经弃用。
c__attribute__ ((__packed__))
告诉编译器不需要字节对齐。省空间。
SDS 之所以设计不同的结构头,是为了能灵活保存不同大小的字符串,从而有效节省内存空间。 否则,假设 SDS 都设计一样大小的结构头,比如都使用 uint64_t 类型表示 len 和 alloc, 那么假设要保存的字符串是 10 个字节,而此时结构头中 len 和 alloc 本身就占用了 16 个 字节了,比保存的数据都多了。所以这样的设计对内存并不友好,也不满足 Redis 节省内 存的需求。
SDS的结构由两部分组成:
以下是flags的值类型
c
// 定义SDS的类型常量
#define SDS_TYPE_5 0 // 5字节类型
#define SDS_TYPE_8 1 // 8字节类型
#define SDS_TYPE_16 2 // 16字节类型
#define SDS_TYPE_32 3 // 32字节类型
#define SDS_TYPE_64 4 // 64字节类型
// 定义SDS类型的掩码和位数
#define SDS_TYPE_MASK 7 // 类型掩码,用于提取SDS的类型
#define SDS_TYPE_BITS 3 // 类型位数,用于表示SDS的类型所需的二进制位数
// 注释:
// SDS是Redis中的简单动态字符串(Simple Dynamic String)实现。
// 宏定义中的SDS_TYPE_5到SDS_TYPE_64表示不同字节长度的SDS类型,
// 其中SDS_TYPE_MASK用于提取SDS的类型信息,
// SDS_TYPE_BITS表示SDS类型所需的二进制位数。
还有获取sds所属结构体的宏
c#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
SDS_HDR_VAR(T,s)
宏定义用于定义并初始化一个名为 sh
的指针变量,该变量指向类型为 sdshdr##T
的结构体,它的值为 (s) - (sizeof(struct sdshdr##T))
。T
是一个占位符,用于在宏定义中拼接出特定类型的结构体名。s
是一个指针,表示 SDS 的起始地址。c#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
SDS_HDR(T,s)
宏定义用于返回一个类型为 sdshdr##T
的结构体指针,该指针指向 SDS 的头部。T
是一个占位符,用于在宏定义中拼接出特定类型的结构体名。s
是一个指针,表示 SDS 的起始地址。c#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
SDS_TYPE_5_LEN(f)
宏定义用于计算 SDS 类型为 SDS_TYPE_5
时的长度。f
是一个字节,表示 SDS 的 flags 字节,其中包含了类型信息。>>
是右移操作符,表示将 f
右移 SDS_TYPE_BITS
位,相当于将 f
的低 SDS_TYPE_BITS
位去掉,只保留高位的类型信息。这些宏定义在 Redis 源码中用于简化对 SDS 结构体的操作。SDS_HDR_VAR
和 SDS_HDR
宏定义用于将指向 SDS 字符串的指针转换为指向 SDS 头部结构体的指针,方便获取头部信息。SDS_TYPE_5_LEN
宏定义则用于从 flags 字节中提取 SDS 类型为 SDS_TYPE_5
时的长度信息。
一些常用的函数的函数
cstatic inline size_t sdslen(const sds s) {
unsigned char flags = s[-1]; // 获取SDS的前一个字节,即flags字节,用于获取SDS的类型信息
switch(flags&SDS_TYPE_MASK) { // 使用掩码将flags字节与SDS_TYPE_MASK进行与操作,获取SDS的类型
case SDS_TYPE_5: // 如果类型为SDS_TYPE_5
return SDS_TYPE_5_LEN(flags); // 返回SDS_TYPE_5_LEN宏定义的值,用于计算SDS的长度
case SDS_TYPE_8: // 如果类型为SDS_TYPE_8
return SDS_HDR(8,s)->len; // 返回SDS_HDR宏定义提取的SDS的len字段,表示SDS的长度
case SDS_TYPE_16: // 如果类型为SDS_TYPE_16
return SDS_HDR(16,s)->len; // 返回SDS_HDR宏定义提取的SDS的len字段,表示SDS的长度
case SDS_TYPE_32: // 如果类型为SDS_TYPE_32
return SDS_HDR(32,s)->len; // 返回SDS_HDR宏定义提取的SDS的len字段,表示SDS的长度
case SDS_TYPE_64: // 如果类型为SDS_TYPE_64
return SDS_HDR(64,s)->len; // 返回SDS_HDR宏定义提取的SDS的len字段,表示SDS的长度
}
return 0; // 默认返回0,表示长度为0
}
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;
}
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;
}
}
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;
}
}
/* sdsalloc() = sdsavail() + sdslen() */
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;
}
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实例怎么创建的
csds sdsnew(const char *init) {
size_t initlen = (init == NULL) ? 0 : strlen(init);
return sdsnewlen(init, initlen);
}
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 (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;
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;
}
SDS的使用示例:
c// 创建一个SDS字符串
sds s = sdsnew("Hello");
// 追加字符串
s = sdscat(s, " World");
// 获取字符串长度
size_t len = sdslen(s);
// 打印字符串
printf("%s\n", s);
// 释放内存
sdsfree(s);
通过SDS,我们可以方便地进行字符串的操作,包括创建、追加、获取长度和释放内存等。
Learn more:
本文作者:yowayimono
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!