文章目录
- 前言
- 代码位置
- 核心类型
- SDS结构
- 获取sds字符串的元数据的宏
- 获取字符串长度
- 重新设置sds长度
- 创建字符串
- 感悟
- 最后
前言
Redis中实现了sds(simple dynamic string)这种字符串,它比c语言标准库的char*字符串更加实用
代码位置
src/sdc.h
src/sdc.c
核心类型
// 底层字符数组
typedef char *sds;// 不同大小的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[];
};
SDS结构
类型别名sds实际就是底层的字符数组
获取sds字符串的元数据的宏
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
获取字符串长度
static inline size_t sdslen(const sds s) {// 根据偏移获取sds类型unsigned char flags = s[-1];// 根据相应sds类型获取sds对应的元数据,最后获取长度switch(flags&SDS_TYPE_MASK) {case SDS_TYPE_5:return SDS_TYPE_5_LEN(flags);case SDS_TYPE_8:return SDS_HDR(8,s)->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;
}
重新设置sds长度
// newlen: sds新长度
static inline void sdssetlen(sds s, size_t newlen) {// 根据偏移获取sds类型unsigned char flags = s[-1];// 根据相应sds类型获取sds对应的元数据,然后设置其长度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;}
}
创建字符串
// initlen: 初始化的长度
sds _sdsnewlen(const void *init, size_t initlen, int trymalloc) {//sds指针void *sh;// sds变量sds s;// 判定sds的类型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. */size_t usable;assert(initlen + hdrlen + 1 > initlen); /* Catch size_t overflow */// 分配内存sh = trymalloc?s_trymalloc_usable(hdrlen+initlen+1, &usable) :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);// 指向sh底层的字符数组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);sh->len = initlen;sh->alloc = usable;*fp = type;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)// 拷贝指定长度的字符串给smemcpy(s, init, initlen);// 设置结束符s[initlen] = '\0';// 返回底层字符数组return s;
}// 创建指定长度的字符串
sds sdsnewlen(const void *init, size_t initlen) {return _sdsnewlen(init, initlen, 0);
}
感悟
- 相比于c语言char*,sds更加易用,可以通过元数据获取信息,无需像char*每次获取长度时进行遍历
- sds可以表示含有“\0”的数据,因为sds是通过长度来界定字符串的,而不是像char*通过"\0"来确定字符串
- sds使用不同类型来表示不同大小的字符串,不同类型的sds使用attribute ((packed))的紧凑型内存布局来节省内存
最后
我是醉墨居士,我会继续分享Redis源码相关内容😊