写点什么

SDS——Redis 源码剖析,java 工程师进阶书籍

用户头像
极客good
关注
发布于: 刚刚

if (sh == NULL) return NULL;


if (init==SDS_NOINIT)


init = NULL;


else if (!init)


memset(sh, 0, hdrlen+initlen+1);


/* 注意:返回的 s 并不是直接指向 sds 的指针,而是指向 sds 中字符串的指针,sds 的指针还需要


  • 根据 s 和 hdrlen 计算出来 */


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 的使用




上面代码中我特意标注了一个注意sdsnewlen()返回的 sds 指针并不是直接指向 sdshdr 的地址,而是直接指向了 sdshdr 中 buf 的地址。这样做有啥好处?好处就是这样可以兼容 c 原生字符串。buf 其实就是 C 原生字符串+部分空余空间,中间是特殊符号'\0'隔开,‘\0’有是标识 C 字符串末尾的符号,这样就实现了和 C 原生字符串的兼容,部分 C 字符串的 API 也就可以直接使用了。 当然这也有坏处,这样就没法直接拿到 len 和 alloc 的具体值了,但是也不是没有办法。


当我们拿到一个 sds,假设这个 sds 就叫s吧,其实一开始我们对这个 sds 一无所知,连他是 sdshdr 几都不知道,这时候可以看下 s 的前面一个字节,我们已经知道 sdshdr 的数据结构了,前一个字节就是 flag,根据 flag 具体的值我们就可以推断出 s 具体是哪个 sdshdr,也可以推断出 sds 的真正地址,相应的就知道了它的 len 和 alloc,知道了这点,下面这些有点晦涩的代码就很好理解了。


oldtype = s[-1] & SDS_TYPE_MASK; // SDS_TYPE_MASK = 7 看下 s 前面一个字节(flag)推算出 sdshdr 的类型。


// 这个宏定义直接推算出 sdshdr 头部的内存地址


#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))


#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)


// 获取 sds 支持的长度


static inline size_t sdslen(const sds s) {


unsigned char flags = s[-1]; // -1 相当于获取到了 sdshdr 中的 flag 字段


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; // 宏替换获取到 sdshdr 中的 len


...


// 省略 SDS_TYPE_16 SDS_TYPE_32 的代码……


case SDS_TYPE_64:


return SDS_HDR(64,s)->len;


}


return 0;


}


// 获取 sds 剩余可用空间大小


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;


}


...


// 省略 SDS_TYPE_16 SDS_TYPE_32 的代码……


case SDS_TYPE_64: {


SDS_HDR_VAR(64,s);


return sh->alloc - sh->len;


}


}


return 0;


}


/* 返回 sds 实际的起始位置指针 */


void *sdsAllocPtr(sds s) {


return (void*) (s-sdsHdrSize(s[-1]));


}


SDS 的扩容




在做字符串拼接的时候,sds 可能剩余的可用空间不足,这个时候需要扩容,什么时候该扩容,又该怎么扩? 这是不得不考虑的问题。Java 中很多数据结构都有动态扩容的机制,比如和 sds 很类似的 StringBuffer,HashMap,他们都会在使用过程中动态判断是否空间充足,而且基本上都采用了先指数扩容,然后到一定大小限制后才开始线性扩容的方式,Redis 也不例外,Redis 在 1024_1024 以内都是 2 倍的方式扩容,只要不超出 1024_1024 都是先额外申请 200%的空间,但一旦总长度超过 1024_1024 字节,那每次最多只会扩容 1024_1024 字节。 Redis 中 sds 扩容的代码是在 sdsMakeRoomFor(),可以看到很多字符串变更的 API 开头都直接或者间接调用这个。 和 Java 中 StringBuffer 扩容不同的是,Redis 这里还需要考虑不同字符串长度时 sdshdr 类型的变化,具体代码如下:


// 扩大 sds 的实际可用空间,以便后续能拼接更多字符串。


// 注意:这里实际不会改变 sds 的长度,只是增加了更多可用的空间(buf)


sds sdsMakeRoomFor(sds s, size_t addlen) {


void *sh, *newsh;


size_t avail = sdsavail(s);


size_t len, newlen;


char type, oldtype = s[-1] & SDS_TYPE_MASK; // SDS_TYPE_MASK = 7


int hdrlen;


/* 如果有足够的剩余空间,直接返回 */


if (avail >= addlen) return s;


len = sdslen(s);


sh = (char*)s-sdsHdrSize(oldtype);


newlen = (len+addlen);


// 在未超出 SDS_MAX_PREALLOC 前,扩容都是按 2 倍的方式扩容,超出后只能递增


if (newlen < SDS_MAX_PREALLOC) // SDS_MAX_PREALLOC = 1024*1024


newlen *= 2;


else


newlen += SDS_MAX_PREALLOC;


type = sdsReqType(newlen);


/* 在真正使用过程中不会用到 type5,如果遇到 type5 直接使用 type8*/


if (type == SDS_TYPE_5) type = SDS_TYPE_8;


hdrlen = sdsHdrSize(type);


if (oldtype==type) {


newsh = s_realloc(sh, hdrlen+newlen+1);


if (newsh == NULL) return NULL;


s = (char*)newsh+hdrlen;


} else {


// 扩容其实就是申请新的空间,然后把旧数据挪过


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码



newsh = s_malloc(hdrlen+newlen+1);


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);


}


sdssetalloc(s, newlen);


return s;


}

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
SDS——Redis源码剖析,java工程师进阶书籍