💮 sds
2022年10月10日
- db
💮 sds
1. 数据结构
__attribute__ ((__packed__))
的作用:- 让编译器 以 紧凑方式 分配内存
- 默认情况下若数据类型不同,比如一个 int 和一个 char 一起存放,则 char 也会是 int 的四字节长度
- 若没有该属性,则编译器可能会为 struct 的字段做优化对齐,在其中填充空字节,
- 这样就无法保证 header & sds 的数据部分严格前后相邻
- 也就无法按照 固定向低地址方向偏移 1 个字节 的方式获取 flag 字段,进而无法得出是哪种 header 类型
- 可以存放任意字符
- C 语言字符串以
\0
即 null 字符作为字符串的终止,因此字符串中间某个字符不能存放 null - 而 sds 采用
{len + alloc + flags + char[]}
的形式,无需通过 null 判断字符串的终止,而是根据长度、类型计算终止位置,因此可以存储任意字符
- C 语言字符串以
- 获取字符串长度复杂度为
O(1)
- 不会发生 缓冲区的溢出
- 在已有字符串后拼接字符串时,首先根据
alloc - len
判断是否有剩余足够空间,若否,则先扩充 alloc 大小 - 在申请新空间时,若 新空间 < 最大可申请大小(1024 * 1024 == 1MB),则新空间大小 == 2 * (原字符串长度 + 追加字符串长度),否则为
1MB
- 减少了扩容的次数
- 在已有字符串后拼接字符串时,首先根据
- 多种 sds 数据结构,节省内存空间
- 可灵活根据不同字符串的大小分配合适的 数据结构存储
- 给定 sds 的结构如何分析?
- 通过 char[] 的地址向前偏移 1 个字节,这个字节就是
flags
所在的字节 - 通过分析 flag 可以得到 header 的类型,判断是
sdshdr5
还是sdshdr8
等等 - 然后就可以推断出 header 对应的地址,然后就可以读取 len, alloc 等内容,再以此截取指定长度的字符数就是存储的字符串了
- 通过 char[] 的地址向前偏移 1 个字节,这个字节就是
typedef char *sds;
/* sdshdr5 从未被使用过 */
// 没有 alloc 字段,无法为字符串分配空余空间
// 若字符串动态增长,必然要重新分配内存
// 适用于存储 静态的短字符串 && len < 2^5
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 一个字节,低 3 位表示 header 的类型,高 5 位记录 字符串长度 */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* 字符串的字符数,不包含 len, alloc, flag 之类,就只是 使用的字符的长度 */
uint8_t alloc; /* 分配给字符串的空间长度,不包含 flag 和 null 结束符,通过 alloc - len 可得出剩余分配空间 */
unsigned char flags; /* 一个字节,低 3 位表示 header 的类型,高 5 位未被使用 */
// 字符数组,长度 == 最大容量 + 1,存放 header & data,这里 + 1 是因为最后要额外添加一个 null(ASCⅡ 对应 '\0') 以便和 C 兼容
// 这里并没有指定数组长度,这时 C 的一种特殊写法,在分配内存时,并不占用内存空间,仅仅是一个标识作用
// 在分配后,若计算 sizeof(struct sdshdr8) 就只会得到前面字段的长度 == 3 字节
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[];
};
// 定义的 header 类型,用 flag 的低 3 位表示,共 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
2. 基础函数
- 包含在
.h
文件中
#define SDS_TYPE_MASK 7 // 用于获取 flag 的低 3 位
#define SDS_TYPE_BITS 3 // header 所占的位数
#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)))) // 获取 header 起始指针
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) // 获取 sdshdr5 的 字符串字符数,即获取 flag 的 高 5 位
// 获取字符串长度
static inline size_t sdslen(const sds s) {
unsigned char flags = s[-1]; // s 前面一个字节表示的是 flags
switch(flags & SDS_TYPE_MASK) { // 与 SDS_TYPE_MASK 想与,只保留低 3 位获取 header 类型
case SDS_TYPE_5:
return SDS_TYPE_5_LEN(flags); // 直接求 flags 的高 5 位就是长度
case SDS_TYPE_8:
return SDS_HDR(8,s)->len; // 得到 header 指针所在地址后,那么指向的就是这个对象了,然后获取 sds.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;
}
// 获取字符串剩余可用空间 == alloc - len
static inline size_t sdsavail(const sds s) {
unsigned char flags = s[-1];
switch(flags & SDS_TYPE_MASK) {
case SDS_TYPE_5: {
return 0; // sdshdr5 一经确定,无法分配动态空间
}
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:
break; // 没有 alloc 这个字段,自然也无法设置其值
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;
}
}
3. 创建
/* 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);
}
// 创建一个初始长度为 initlen 的字符串
sds sdsnewlen(const void *init, size_t initlen) {
return _sdsnewlen(init, initlen, 0);
}
sds _sdsnewlen(const void *init, size_t initlen, int trymalloc) {
void *sh;
sds s;
char type = sdsReqType(initlen); // 根据给定初始长度,确定使用哪种 sds
// 如果得出是要用 sdshdr5 那么需要采用 sdshdr8,因为空字符串创建后一般都需要追加数据,因此 dsshdr5 不太合适
if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
int hdrlen = sdsHdrSize(type); // 当前类型 sds 的内存大小,这里 char[] 还未分配,因此得到的是 header 的大小
unsigned char *fp; /* flags 指针 */
size_t usable;
assert(initlen + hdrlen + 1 > initlen); /* Catch size_t overflow */
sh = trymalloc?
s_trymalloc_usable(hdrlen+initlen+1, &usable) : // 尝试分配内存 == header + initlen + null 结束符
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); // 获取 sds header 地址
sh->len = initlen; // 修改 len
sh->alloc = usable; // 修改 alloc
*fp = type; // 设置 flag 类型
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'; // 最后一个有效字符是 null,和 C 兼容
return s;
}
// 根据类型获取 header 的大小
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 结构
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)
if (string_size < 1ll<<32)
return SDS_TYPE_32;
return SDS_TYPE_64;
#else
return SDS_TYPE_32;
#endif
}
// 根据指定 sds 类型,获取 能够存放的最大字符数
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 */
}
4. 字符串拼接
/* 在 s 后面追加 t
*/
sds sdscatsds(sds s, const sds t) {
return sdscatlen(s, t, sdslen(t));
}
/* 在 s 后面追加 len 长度的 t */
sds sdscatlen(sds s, const void *t, size_t len) {
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 s;
}
/* 申请多余 所需空闲空间 的大小,这样可以避免频繁的追加数据导致的 频繁的 内存申请 */
sds sdsMakeRoomFor(sds s, size_t addlen) {
return _sdsMakeRoomFor(s, addlen, 1);
}
/*
* 为字符串申请足够的空间
* 若空间本身已经足够,那么无需任何操作;若空间不足以放置 addlen,那么需要申请更多的空间
* greedy == 1 时,申请比 所需更大的空间,为了防止未来频繁的内存申请
* greedy == 0 时,申请足够 所需空间即可
* 该函数不会改变通过 sdslen() 得到的字符串大小,而仅仅是申请了内存
*/
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;
/* 1. 有足够空间,直接返回 */
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; // 申请后总空间大小 == 2 * (原字符串长度 + 追加字符串长度)
else
newlen += SDS_MAX_PREALLOC; // 申请后总空间大小 == 最大可申请大小(1024 * 1024 == 1MB)
}
type = sdsReqType(newlen); // 获取新数组大小对应的 sds 类型
/* 若为 sds5,变为 sds8 */
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 {
/* 类型改变,整个字符串都需要重新分配 */
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;
}
#define SDS_MAX_PREALLOC (1024*1024)