Redis的底层数据结构-String(IT枫斗者)
Redis的数据类型我们都知道有五个:String、List、Hhash、Set、Zset。
今天跟大家要说的是它们中string的底层数据结构。
一,底层数据结构

二,必知必会的铺垫
Redis对象,干嘛的?想象成对象头,不管你什么类型,都必须要带的,里面包含数据类型等等信息。

一个RedisObject占用的字节数:4+4+24+32+64=128位/8=16字节。
三、String数据结构
sds/int/embstr/raw
1、SDS
1.1、sds概念
sds(simple dynamic string):简单动态字符串。SDS只是字符串类型中存储字符串内容的结构,
Redis中的字符串分为两种存储方式,分别是embstr和raw。
sds中包含了free(当前可用空间大小),len(当前存储字符串长度),buf[] (存储的字符串内容),来看下
SDS的源码:


1.2、sds干啥的?
比如你 set abc abcdefg ,简单的一个set会创建出两个sds,一个存key:abc,一个存value:abcdefg。比如如下:

1.3、为什么要有sds?
带着问题看答案:C语言中也有字符串类型,为啥它不用C的,反正它都是C语言写的,为啥要造个轮子sds?
1.3.1、优化获取字符串长度
C语言要想获取字符串长度必须遍历整个字符串的每一个字符,然后自增做累加,时间复杂度为O(n);sds直接维护了一个len变量,时间复杂度为O(1)。
1.3.2、减少内存分配
当我们对一个字符串类型进行追加的时候,可能会发生两种情况:
当前剩余空间(free)足够容纳追加内容时,我们就不需要再去分配内存空间,这样可以减少内存分配次数。
当前剩余空间不足以容纳追加内容,我们需要重新为其申请内存空间。
比如下面的sds的方式,free还有三个空余空间呢,你插入的是hi两个字符,所以足够,不需要调用函数重新分配,提升效率。

而C语言字符串在进行字符串的扩充和收缩的时候,都会面临着内存空间的重新分配问题。如果忘记分配或者分配大小不合理还会造成数据污染问题。那么sds的free值哪来的呢?也就是字符串扩容策略
当给sds的值追加一个字符串,而当前的剩余空间不够时,就会触发sds的扩容机制。扩容采用了空间预分配的优化策略,即分配空间的时候:如果sds 值大小< 1M ,则增加一倍;反之如果>1M , 则当前空间加1M作为新的空间。
当sds的字符串缩短了,sds的buf内会多出来一些空间,这个空间并不会马上被回收,而是暂时留着以防再用的时候进行多余的内存分配。这个是惰性空间释放的策略
1.3.3、惰性释放空间
当我们截断字符串时,Redis会把截断部分置空,只保留剩余部分,且不立即释放截断部分的内存空间,这样做的好处就是当下次再对这个字符串追加内容的时候,如果当前剩余空间足以容纳追加内容时,就不需要再去重新申请空间,避免了频繁的内存申请。暂时用不上的空间可以被Redis定时删除或者惰性删除。
1.3.4、防止缓冲区溢出
其实和减少内存分配是成套的,都是因为sds预先检查内存自动分配来做到防止缓冲区溢出的。比如:
程序中有两个在内存中紧邻着的 字符串 s1 和 s2,其中s1 保存了字符串“redis”,s2 则保存了字符串“MongoDb”:

1.3.5、二进制安全
在C语言中通过判断当前字符是否为'\0'来确定字符串是否结束,而在sds结构中,只要遍历长度没有达到len,即使遇到'\0',也不会认为字符串结束。比如下面内存,C语言的字符串类型会丢失g123这四个字符,因为他遇到'\0'就结束了,而sds不会存在此问题。


1.3.6、与C总结

2、int
如果一个字符串内容可转为 long,那么该字符串会被转化为 long 类型,redisObject的对象 ptr 指向该long,并将 encoding 设置为 int,这样就不需要重新开辟空间,算是长整形的一个优化。
3、embstr/raw
上面的SDS只是字符串类型中存储字符串内容的结构,Redis中的字符串分为两种存储方式,分别是embstr和raw,当字符串长度特别短(redis3.2之前是39字节,redis3.2之后是44字节)的时候,Redis使用embstr来存储字符串,而当字符串长度超过39(redis3.2之前)的时候,就需要用raw来存储,下面是他们的字符串完整结构的示意图:


4、总结
redis的string底层数据结构使用的是sds,但是sds有两种存储方式,一种是embstr,一种是raw。
embstr的优势在于和对象头一起分配到连续空间,只需要调用函数malloc一次就行。raw需要两次,一次是对象头,一次是sds。释放也一样,embstr释放一次,raw释放两次。
字符串内容可转为 long,采用 int 类型,否则长度<39(3.2版本前是39,3.2版本后分界线是44) 用embstr,其他用 raw。
SDS 是Redis自己构建的一种简单动态字符串的抽象类型,并将 SDS 作为 Redis 的默认字符串表示。
SDS 与 C 语言字符串结构相比,具有四大优势。