sds
在redis中,存储字符串的结构称为 sds (Simple Dynamic String) 简单动态字符串
在源码sds.h中定义如下:
typedef char *sds;
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags;
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len;
uint8_t alloc;
unsigned char flags;
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[];
};
复制
可以看出,sds其实只是一个char 的一个别名,实现sds的完整数据结构包括了 sdshdr\ 的结构
attribute ((packed))
这个语法是gcc编译器的特有的语法,表示结构体将不使用内存对齐技术,而是使用紧凑模式存储结构体,例如:
#include<stdio.h>
#include<stdlib.h>
#include <string.h>
struct Test1 {
char a;
uint8_t b;
uint32_t c;
uint64_t d;
};
struct __attribute__ ((__packed__)) Test2 {
char a;
uint8_t b;
uint32_t c;
uint64_t d;
};
int main() {
printf("%ld\n", sizeof(struct Test1));
printf("%ld\n", sizeof(struct Test2));
return 0;
}
复制
在这个例子中,内存存储为:
在Test1 中,最大的直接为unit64 占用8字节,为了内存对齐,则会额外分配一个新的8字节,用于存储其他属性,其他属性如果存的下就存,存不下就开辟新的8字节,用于内存对齐
而在Test2中,使用了紧凑模式,字节数等于成员占用内存数,节省了一部分内存
sds存储结构
在sds中,8的存储结构如下:
typedef char *sds;
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len;
uint8_t alloc;
unsigned char flags;
char buf[];
};
复制
如图:
可以看到,sdshdr8的内存地址,包含了len,alloc,flags,buff则存储了sds的指针和字符串数据,那么可以发现,sds指针-1的位置,是flag
unsigned char flags = s[-1];
复制
flags低三位表示类型,通过flags能知道具体存储的类型:
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;
}
复制
通过类型,可以知道前面的结构体的类型,从而知道结构体的长度,sds的指针位置-结构体长度=sdshdr的指针位置:
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
复制
这样,我们就知道了sds和sdshdr之间的关系
创建字符串
申请长度为 sdshdr结构体长度+字符串长度+1的内存空间,为什么需要+1呢?因为存储字符串时,会额外存储 "\0"用于结尾:
sh = trymalloc?
s_trymalloc_usable(hdrlen+initlen+1, &usable) :
s_malloc_usable(hdrlen+initlen+1, &usable);
复制
这样,sds字符串的指针位置,就等于 sdshdr指针+sdshdr长度
最后,通过memcpy,将字符串的值复制到sds的上,加上\0,就完成了
if (initlen && init)
memcpy(s, init, initlen);
s[initlen] = '\0';
复制
动态扩容字符串
当需要动态扩容字符串时,先计算新的长度数量,贪婪模式下,将扩充为2倍:
if (greedy == 1) {
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
}
复制
计算新长度需要使用的sdshdr结构体类型,判断旧结构体类型是否一致
如果类型一致,则只需要额外分配新的内存空间即可
if (oldtype==type) {
newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);
if (newsh == NULL) return NULL;
s = (char*)newsh+hdrlen;
}
复制
如果不一致,则需要类似于新增的逻辑,重新分配一块新的内存空间,将旧数据拷贝到新空间中
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);
复制
评论(0)