REDIS 数据类型之 SDS
SDS分配一段连续的内存来存储动态字符串
1.SDS的数据结构
- len: 记录buf数组中已使用字节的数量 , 等于 SDS 保存字符串的长度.
- alloc: 分配的字符数量(可用长度)
- flags: 类型标志位
- buf[]: 存储的字符串内容
typedef char *sds;
//SDS的类型
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
//适配不同长度
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; //字符串真实长度,不包括终止符
uint8_t alloc; //字符串最大容量,不包括终止符
unsigned char flags; //head的类型
char buf[]; //字符串主体
};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len;
uint16_t alloc;
unsigned char flags;
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len;
uint32_t alloc;
unsigned char flags;
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len;
uint64_t alloc;
unsigned char flags;
char buf[];
};
2.SDS的优点
- 获取字符串长度O(1) , 数据结构中len中存储了SDS 保存字符串的长度.
- 防止缓冲区溢出, C 字符串不记录自身的长度 ,所以字符串复制的时候,可能会产生缓冲区溢出.
- 优化动态字符串的内存分配
- 空间预分配: 对SDS修改时,会分配len长度(最大1M)的空间,当SDS连续增长时,空间分配次数会减少.
- 惰性空间释放: SDS进行缩短操作时,不会重新分配内存,而是使用free属性存储未使用的字节数
- 二进制安全,如果保存二进制数据,遇到 ‘\0’ ,会认为字符结束,导致数据丢失.SDS可以根据len来判断是否结束.
3.SDS的创建
init属性: 要存储的字符串
initlen属性: 存储字符串的长度
sdsReqType: 根据长度 获取初始 type,type为SDS_TYPE_5/8/16/32/64,用于适配不同的长度
sdsHdrSize: 计算SDS的Header部分的长度.
s_malloc: 申请分配内存空间,申请的空间为header长度+主体长度 + 1(结束符 ‘\0’)
memset: 初始化内存, 将指针变量 s 所指向的前 n 字节的内存单元用一个“整数” c 替换
memcpy: 内存复制, 从src的开始位置拷贝n个字节的数据到dest.
sds sdsnewlen(const void *init, size_t initlen) {
void *sh;
sds s;
// 根据长度 获取初始 type
char type = sdsReqType(initlen);
// 空的字符串通常被创建成 type 8,因为 type 5 已经不实用了
if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
// 获取 header 长度
int hdrlen = sdsHdrSize(type);
unsigned char *fp;
// 创建内存空间,空间大小等于 header长度+主体长度 + 1,后面加1是因为需要追加结束符,兼容 C 字符串
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);
// 主体内容 指针地址,相当于整个 SDS 结构体向后偏移了整个 header 长度
s = (char*)sh+hdrlen;
// flags 指针为 主体 SDS 内容向前偏移1位(这个上面我们已经解释过了)
fp = ((unsigned char*)s)-1;
// 根据 type 值进行 header 各个字段的初始化
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);
// 兼容 C 函数,在 字符串后添加结束符
s[initlen] = '\0';
return s;
}
总结:
- 根据存储长度获取type(5/8/16/32/64)
- 创建内存空间hdrlen+initlen+1(header长度+主体长度 +’\0’)
- 初始化内存空间,默认都是’0’
- 设置header的len/alloc
- 通过memcpy设置字符串值
- 设置最后一位为C语音字符串结束符
4.SDS惰性删除
sds的删除并不是直接回收内存,而是修改字符,让其为空字符,这其实是惰性释放 .
下次使用时就可以不用再申请空间.
void sdsclear(sds s) {
sdssetlen(s, 0);
s[0] = '\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;
}
}
5.SDS的扩容
sds sdscat(sds s, const char *t) {
return sdscatlen(s, t, strlen(t));
}
sds sdscatlen(sds s, const void *t, size_t len) {
//获取sds当前的长度
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;
}
sds sdsMakeRoomFor(sds s, size_t addlen) {
return _sdsMakeRoomFor(s, addlen, 1);
}
sds _sdsMakeRoomFor(sds s, size_t addlen, int greedy) {
void *sh, *newsh;
//剩余可用长度
size_t avail = sdsavail(s);
size_t len, newlen;
char type, oldtype = s[-1] & SDS_TYPE_MASK;
int hdrlen;
size_t usable;
//剩余长度还够用,不需要扩容
if (avail >= addlen) return s;
len = sdslen(s);
sh = (char*)s-sdsHdrSize(oldtype);
//sds需要长度
newlen = (len+addlen);
assert(newlen > len);
if (greedy == 1) {
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
}
type = sdsReqType(newlen);
if (type == SDS_TYPE_5) type = SDS_TYPE_8;
hdrlen = sdsHdrSize(type);
assert(hdrlen + newlen + 1 > len);
//类型扩容后是否发生了变化
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;
//1M,大于1M扩容1M,小于扩容双倍
if (usable > sdsTypeMaxSize(type))
usable = sdsTypeMaxSize(type);
sdssetalloc(s, usable);
return s;
}
//计算剩余可用长度(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;
}
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;
}
能摸鱼就很舒服
Show Disqus Comments
扫码关注公众号:纯洁的微笑
发送 290992
即可立即永久解锁本站全部文章