前言
今天开始更新一些redis相关的知识点,初步计划是redis的数据类型,以及redis分布式锁
本章节主要是redis的String类型
字符串SDS
一般情况下我们认为的redis 字符串就是String,但是我这边要说的是底层String类型。
redis底层是C语言,但是C语言中的String有一定的缺陷,所以redis开发了一个自己的字符串类型 SDS 也就是simple dynaic string,专门的数据结构,保证字符串的完整性 动态字符串
c语言中的字符串是 r,e,d,i,s,\0 这种n+1的形式存储的,读取的时候一个个的读取,一直到 \0 这个数据
获取字符串长度的时候,还要遍历一遍
redis sds的字符串类型如下
struct sdshdr { int len; // 已使用字节数= 字符串长度 int free; // 未使用字节数 char buf[]; // 字符串本身内容 }
比如:
sdshdr
free=0
len=5
buf-> [r,e,d,i,s,\0]
c字符串里规定除了末尾可以有一个\0空字符以外,内容里不能包含空字符,否则读到空字符就认定这个字符串结束了,所以导致c字符串只能保存文本,不能保存图片和音视频一类的二进制数据
二进制问题
re\0di\0s
re di s
如果你要是在内容里包含了很多空字符的话,你就不是二进制安全的了
但是sds实现了二进制安全,而且允许内容里包含\0,这是为什么呢,因为sds里有一个len,他是根据len来读取指定字节数量的字符的,而不是根据\0来判断字符串是否结束了,这是一个很关键的优化,通过这个就可以实现二进制安全了
但是redis还是遵守了末位是\0的c规范,这样保存的字符串,就可以复用c语言的一些函数了,因为c语言函数是这么规定的
c语言原生字符串,strlen,获取字符串长度,遍历,O(n),二进制不安全的,对于二进制数据来说,图片、视频、音频、压缩文件,这种数据都是以一大堆的二进制串来存储的,里面肯定会有很多\0空字符,对于c语言来说,读取的时候,一定要按照\0来截断,肯定不行的
二进制不安全,原生的c语言字符串没法安全的存储二进制数据
redis里面的sds,他里面有两个关键的字段,len和free,buf = [] 跟c语言没太大差别,len和free很厉害,free的作用,我们还没讲,len有2个作用,直接读取len,strlen,O(1),读取buf里的数据,不是遍历看到\0就停止
根据len读取指定字节数量的buf数字的内容,形成一个完整的内容,对于我们来说,redis里的sds存储二进制的数据,图片、音视频,就算buf里包含了很多\0空字符,根据len来读取,不是根据\0来截断,redis sds可以实现二进制安全性,安全的保存二进制数据
避免缓冲区溢出
redis和spark两个字符串,在内存地址里,连续的存储在了一起
[r,e,d,i,s,\0,s,p,a,r,k,\0]
str = redis
strcat(str, sentinel) -> 插入忘记了给sentinel字符串分配内存空间
c语言原生字符串可能搞出缓冲区溢出的问题,那就是说,r,e,d,i,s,s,p,a,r,k,连续两个字符串,redis和spark,结果我们要是用c语言的strcat函数,搞了一个strcat(str, ‘sentinel’),想要把sentinel拼接到redis字符串后面去,又忘了给sentinel提前分配内存空间,就会导致,r,e,d,i,s,s,e,n,t,i,n,e,l,直接覆盖掉spark了,这就是缓冲区溢出
[r,e,d,i,s,\0,s,e,n,t,i,n,e,l,\0]
sds(free=0,len=5,buf[r,e,d,i,s,\0]) -> sds(free=13,len=13,buf[r,e,d,i,s,s,e,n,t,i,n,e,l,\0])
sds会自动检查字符串扩容空间是否足够,不够就扩容空间,扩容完毕了再修改字符串的内容,比如sdscat就是这样的拼接字符串的函数,比如一开始sds的free=0,len=5,buf=redis,但是现在你要扩容加上sentinel了,此时就会先扩容,free=13,len=13,buf=redissentinel
内存预分配
除了扩容,他还会额外的给13个自己的free空间
c字符串在拼接或者截断的时候,要不然就得扩容数组,要不然就是释放空间内存,就是有频繁的内存重分配动作,这个内存重分配是很耗费时间的,涉及到os系统调用,redis频繁修改字符串,频繁内存重分配,那就真的是很尴尬
sds为了避免这种频繁内存重分配的问题,才设计了free,这个free可以实现内存预分配和惰性释放,每次sds的内存空间要进行扩容的时候,扩容完毕后,都会根据一个公式,提前计算出free,预分配一定的内存空间出来
扩容后,sds长度小于1mb,就把free设置为跟len一样,做一个双倍预分配,如果扩容后len大于1mb了,就把free设置为1mb就可以了,毕竟不能无限制的扩容下去,这个预分配后,就是底层c里的数组,留了一定字节数量的空间,但是没有真正使用
这样你后续再修改字符串,直接就用free空间就可以了,避免了频繁重分配内存了
惰性释放
如果缩短一个字符串,那么就会把字符串正常缩短,然后len设置为字符串长度,free就是原长度-现长度,以后如果要增长,还可以用free,如果确实要释放内存空间,sds提供了api来释放,但是他是不会立即释放掉的。而是在过一段时间之后,发现空间不足了,才会释放掉这部分空间