一文带你快速搞懂动态字符串SDS,面试不再懵逼

数据安全,不会截断

如果传统字符串保存图片,视频等二进制文件,中间可能出现’\0’,如果按照原来的逻辑,会造成数据丢失。所以可以用已用长度来表示是否字符数组已结束。

SDS关键代码分析


获取常见值(抽象出常见方法)

在sds.h中写了一些常见方法,比如计算sds的长度(即sdshdr的len),计算sds的空闲长度(即sdshdr的可用长度alloc-已用长度len),计算sds的可用长度(即sdshdr的alloc)等等。但是大家有没有疑问,这不是一行代码搞定的事吗,为啥要抽象出方法呢?那么问题在于在上面,我们有将sdshdr分为五种类型,分别是sdshdr5,sdshdr8,sdshdr16,sdshdr32,sdshdr64。那么我们在实际使用的时候,想要区分当前是哪个类型,并取其相应字段或设置相应字段。

//计算sds对应的字符串长度,其实上取得是字符串所对应的哪种sdshdr的len值

static inline size_t sdslen(const sds s) {

// 柔性数组不占空间,所以倒数第二位的是flags

unsigned char flags = s[-1];

//flags与上面定义的宏变量7做位运算

switch(flags&SDS_TYPE_MASK) {

case SDS_TYPE_5://0

return SDS_TYPE_5_LEN(flags);

case SDS_TYPE_8://1

return SDS_HDR(8,s)->len;//取上面结构体sdshdr8的len

case SDS_TYPE_16://2

return SDS_HDR(16,s)->len;

case SDS_TYPE_32://3

return SDS_HDR(32,s)->len;

case SDS_TYPE_64://5

return SDS_HDR(64,s)->len;

}

return 0;

}

//计算sds对应的空余长度,其实上是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;

}

//设置sdshdr的len

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;

}

}

//给sdshdr的len添加多少大小

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;

}

}

//获取sdshdr的总长度

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;

}

//设置sdshdr的总长度

static inline void sdssetalloc(sds s, size_t newlen) {

unsigned char flags = s[-1];

switch(flags&SDS_TYPE_MASK) {

case SDS_TYPE_5:

/* Nothing to do, this type has no total allocation info. */

break;

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;

}

}

创建对象

我们通过sdsnew方法来创建对象,显示通过判断init是否为空来确定初始大小,接着调用方法sdsnew(这边方法名一样,但是参数不一样,其为方法的重载),先根据长度确定类型(上面有提过五种类型,不记得的可以往上翻),然后根据类型分配相应的内存资源,最后追加C语言的结尾符’\0’。

sds sdsnew(const char *init) {

size_t initlen = (init == NULL) ? 0 : strlen(init);

return sdsnewlen(init, initlen);

}

sds sdsnewlen(const void *init, size_t initlen) {

void *sh;

sds s;

char type = sdsReqType(initlen);//根据长度确定类型

/*空字符串,用sdshdr8,这边是经验写法,当想构造空串是为了放入超过32长度的字符串 */

if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;

int hdrlen = sdsHdrSize(type);//到下一个方法,已经把他们放在一起了

unsigned char fp; / flags pointer. */

//分配内存

sh = s_malloc(hdrlen+initlen+1);

if (!init)

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

if (sh == NULL) return NULL;

s = (char*)sh+hdrlen;

fp = ((unsigned char*)s)-1;

//根据不同的类型,创建不同结构体,调用SDS_HDR_VAR函数

//为不同的结构体赋值,如已用长度len,总长度alloc

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

//最后追加’\0’

s[initlen] = ‘\0’;

return s;

}

//根据实际字符长度确定类型

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;

#endif

return SDS_TYPE_64;

}

删除

String类型的删除并不是直接回收内存,而是修改字符,让其为空字符,这其实是惰性释放,等待将来使用。在调用sdsempty方法时,再次调用上面的sdsnewlen方法。

/*修改sds字符串使其为空(零长度)。

*但是,所有现有缓冲区不会被丢弃,而是设置为可用空间

*这样,下一个append操作将不需要分配到

*当要缩短SDS保存的字符串时,程序并不立即使用内存充分配来回收缩短后多出来的字节,并等待将来使用。

void sdsclear(sds s) {

sdssetlen(s, 0);

s[0] = ‘\0’;

}

sds sdsempty(void) {

return sdsnewlen(“”,0);

}

添加字符(扩容)重点!!!

添加字符串,sdscat输入参数为sds和字符串t,首先调用sdsMakeRoomFor扩容方法,再追加新的字符串,最后添加上结尾符’\0’。我们来看下扩容方法里面是如何实现的?第一步先调用常见方法中的sdsavail方法,获取还剩多少空闲空间。如果空闲空间大于要添加的字符串t的长度,则直接返回,不想要扩容。如果空闲空间不够,则想要扩容。第二步判断想要扩容多大,这边有分情况,如果目前的字符串小于1M,则直接扩容双倍,如果目前的字符串大于1M,则直接添加1M。第三个判断添加字符串之后的数据类型还是否和原来的一致,如果一致,则没啥事。如果不一致,则想要新建一个sdshdr,把现有的数据都挪过去。

这样是不是有点抽象,举个例子,现在str的字符串为hello,目前是sdshdr8,总长度50,已用6,空闲44。现在想要添加长度为50的字符t,第一步想要看下是否要扩容,50明显大于44,需要扩容。第二步扩容多少,str的长度小于1M,所以扩容双倍,新的长度为50*2=100。第三步50+50所对应sdshdr类型还是sdshdr8吗?明显还是sdshdr8,所以不要数据迁移,还在原来的基础上添加t即可。

sds sdscat(sds s, const char *t) {

return sdscatlen(s, t, strlen(t));

}

sds sdscatlen(sds s, const void *t, size_t len) {

//调用sds.h里面的sdslen,即取已用长度

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

void *sh, *newsh;

//调用sds.h,获取空闲长度alloc

size_t avail = sdsavail(s);

size_t len, newlen;

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

int hdrlen;

//空闲长度大于需要增加的,不需要扩容,直接返回

if (avail >= addlen) return s;

//调用sds.h里面的sdslen,即取可用长度

len = sdslen(s);

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

//len加上要添加的大小

newlen = (len+addlen);

//#define SDS_MAX_PREALLOC (1024*1024)

//当新长度小于 1024*1024,直接扩容两倍

if (newlen < SDS_MAX_PREALLOC)

newlen *= 2;

else //当新长度大于 10241024,加20141024

newlen += SDS_MAX_PREALLOC;

//根据长度计算新的类型

type = sdsReqType(newlen);

/* Don’t use type 5: the user is appending to the string and type 5 is

  • not able to remember empty space, so sdsMakeRoomFor() must be called

  • at every appending operation. */

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
ery appending operation. */

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-uhPwHQDX-1715184491832)]

[外链图片转存中…(img-rem75bxG-1715184491832)]

[外链图片转存中…(img-wdkzIoRG-1715184491833)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 26
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值