上一篇我们熟悉了SDS字符串的基本结构以及一些基本方法,我们接下来继续深入看一些方法比较重要的方法,别忘了我们的目的是为了找出SDS到底比glibc的字符串好在哪儿。

具体实现

以下代码位于sds.c


image.png
1555073913731569.png

首先说明一下这个工具函数,该函数会根据参数string_size来判断该string_size下应该用什么类型的sdshdr*结构,因为sdshdr*中不同类型的len字段可以表示的最大len值不一样,从这一点可以很明显的感受到sds在设计的时候是非常考虑空间节省的,不过对于一个基础库来说就是应该这样。


image.png
1555065488949458.png

显然这是用于创建sds字符串的函数

参数len是新创建的sds字符串的长度

参数init非空时,表示会将init指向的内容中[init, init + len]这段区间上的内容拷贝到新创建的sds字符串上;参数init为空时,则直接把新创建的sds字符串的内容填充为'\0'。

再次可以明确,sds字符串的长度任何时候都不会以'\0’作为标识终止,其真正的长度是由hdr中的len字段决定,因为是二进制安全,但是从倒数第三行也可以看到,sds字符串仍然在结尾会放一个'\0'。

以下是几个相关的几个函数,非常简洁,其作用都非常显然,并且在源码中都有清晰的解释,就不赘述了。

image.png
1555069201105942.png

注意一下这个函数

image.png
1555072641959230.png

这个函数用于更新sds字符串的长度,但是并不是用入参s的len字段,而是用strlen来找出s中的第一个'\0’之前的那段长度,也是把入参s当做一个glibc的字符串来更新长度,不要想当然的以为就是用len来更新。

接下来我们看这个clear函数

image.png
1555072850883829.png

它与free是绝对区分开的,这是非常方便的,glibc可没有这种函数,glibc的字符串想二进制安全的去清空一个字符串几乎是不可能的,因为通过strlen只判断到'\0'(注意我们这个严格的指字符串非字符数组),(Redis可对C++没兴趣),因此这个有点还是归到上篇中我们的第一点二进制安全。


image.png
1555073624488224.png

一个方便sds字符串扩容函数,入参addlen就是的长度。

这是一个非常“聪明的”函数

“聪明”在这几个点

1、当剩余可用空间>=addlen时,直接返回,不必扩容

2、当addlen小于阈值SDS_PRE_ALLOC时,每次直接给容量alloc加上2倍addlen,若超过阈值,则增加一个阈值的容量,阈值的大小默认为1MB如下

image.png
1555080693297459.png

因此要注意到扩容后的容量alloc远远大于原长度len + addlen,扩容后的长度len当然还是原来的len,因为这是扩充容量。

(事实上这种增长方式非常常见,许多线性连续存储的容器都是采用这方式,我们在此姑且把sds也当成一种容器)

3、良好的自动类型提升

我们的第二大优点来了

SDS优点 2:高性能的扩容,极大减小内存分配次数,避免时间消耗与减少内存碎片


image.png
1555169232752359.png

这个函数用于删除sds字符串中alloc-len的那段未使用的空闲空间,其实现我们通过上面的系列剖析后就显得很简洁了,不加以赘述,唯一需要注意的是这函数的入参将是非法的,应该使用其返回值来进行后续操作。


image.png
1555170022904065.png

这个函数没什么特别的,增加sds字符串的实际长度len,这个函数的标准使用如下,来自作者的解释:

image.png
1555170080471937.png


下面是个比较有意思且常见的函数,相信很多人都知道有道把数字转化为字符串的面试题,咱们不妨看看作者会怎么写

image.png
1555170271704628.png

嘿嘿嘿,似乎写这个函数再厉害的大佬也是这样写呀。


接下来的要说的,是个人认为sds最棒的地方之一,非常熟悉C/C++的人看到这些方法估计真的可能会想glibc当初在搞什么到底。

那就是sds真的太好了用了!!!

image.png
1555170815200221.png

看看这个,连接字符串,可以按格式化printf的方式!C++的string库竟然都不提供这一点,令人费解。事实上这个函数正如它的名字,其依赖了printf( )函数族中的vsnprintf( ),在此我不进行展开那部分代码,我想说的是,你可能嫌vsnprintf函数慢,没关系,sds有一套直接操作内存的实现如下,该方法具体实现过于冗长,且具有重复性,截取部分

image.png
1555171173177286.png

下面我们再看看还有哪些好东西

image.png
1555171272480845.png

修整字符串两端,这个方法有多么重要我想不必多言,然而人家glibc就是没有(微笑)。

image.png
1555171390152782.png

范围截断且支持反向,glibc是不可能有的

继续看下面这个方法你会更加感觉sds的美好~

image.png
1555171511662699.png

split!!熟悉C/C++的人再次泪流满面,曾几何时,突然想用按某个符号分割以下字符串,可glibc就是没有(微笑),你可以发现网上有各种关于split功能的奇怪实现,C/C++程序员有时候苦逼起来真不是python能理解的,越发感觉作者在写这个库的时候对glibc的怨念(你没有的在下全部写好=_=)

看看下面这个函数,发自内心的感慨,sds真美好啊~

image.png
1555171969497631.png

这个实现了这样一个映射转换功能:

例如我们的操作对象字符串为"hi liufeihao",from为"le",to为"ab",那么s会被修改为"hi aiufbihao",所有from中的字符被映射替换为了to中对位置的字符,这连python的字符串库都没有原生支持这样的操作!

到此为止,sds的最后一个优点,也是最重要的一点如下,其内容非常简洁

SDS优点 3:非常好用


SDS的优点总结

  • SDS优点 1:O(1)复杂度获取字符串长度,同时不以'\0’识别结尾因此二进制安全

  • SDS优点 2:高性能的扩容,极大减小内存分配次数,避免时间消耗与减少内存碎片**

  • SDS优点 3:非常好用


以后若是用C/C++开发项目时考虑向你同事引入一下SDS?(好吧我知道你们没有写C/C++的机会…..)