redis-原理-数据结构-SDS(一)
什么是SDS
1.redis没有直接使用C的字符串,而是自己实现了字符串的实现名叫SDS
2.c的字符串只会用在值不会改变的地方,比如redislog打印
SDS的应用场景
1.
本地:0>set msg hellowrd
OK
键和值都是字符串对象,底层通过SDS实现
2.
本地:0>rpush fruits apple banana cherry
3
键是字符串对象 由SDS实现, 值是列表对象,列表对象包含三个字符串对象 分别由SDS实现
SDS结构定义
SDS遵循C的字符串定义,以空字符串(\0)作为字符串的结尾,空字符串1字节不计算在len里面,好处就是直接使用C字符串的函数如:print(%,s-buf)
带有未使用字节数量的SDS
SDS和C字符串的区别
1. C字符结构是没有记录长度的,如果要获取长度必须遍历字符,复杂度是0(N)
2.SDS是记录了字符串长度获取字符串长度是0(1)如:
本地:0>strlen msg
8
缓冲区溢出
因为C不记录字符长度,如果在进行扩容时,只能假定内存足够,如果不足够则会造成缓冲区溢出
扩容前 S1 和S2 2个字符紧邻,我们要对S1进行扩容,因为没有记录长度我们并不知道内存是否足够,执行:strcat(s1,"cluster")
扩容前:
扩容后
SDS如何解决缓冲区溢出
与C不同因为SDS记录了未使用空间,在扩容前会先判断空间是否足够,如果不够sdscat(redis SDS的拼接API)先扩容空间
如执行
strcat(s1,"cluster")
扩容前
扩容后
二进制安全
C的字符必须符合某种编码(ASCII),并且除了字符串末尾之外字符串里面不能包含空字符,否则会误认为字符串结尾,这些使得C只能保存文本而不能保存图片、视频、音频、压缩文件这样的二进制数据
因为SDS并没有使用空字符(\0)判断字符的结尾而是len来判断,使用SDS可以保存图片 视频 音频等二进制数据
SDS对于扩容的优化
空间预分配
当我们对SDS空间进行扩展的时候,SDS除了分配必要的空间,还会为SDS分配额外的未使用空间 free记录。
公式:
1.如果对SDS进行修改后,len长度小于1M则分配len同样大小的未使用空间 len+free+1为当前SDS占用空间(小于1m会预分配lenth长度空间)1为结尾空字符占用空间
2.如果对SDS进行修改有len长度大于1M则会额外分配1M未使用空间 len+1m+1为当前SDS占用空间(大于1m,则固定分配1m,避免扩展太多空间) 1为结尾空字符占用空间
当每次扩展SDS API都会检查free是否有足够空间,如果有则不执行扩展 直接使用 减少了分配次数,
通过空间预分配优化,将连续增长N次字符串需要扩展N次,变为最多N次
惰性分配
当SDS缩短字符长度时候,并不会完全释放,只是将多出来的空间记录到free
如果移除字符串中所有的 x和y
移除前
移除后
将来需要拼接字符时则可以减少扩容
预分配和惰性分配空间浪费解决方式
通过上面的意思,会让我们觉得会浪费空间,其实SDS也提供了API让我们可以真正的释放SDS未使用空间,所以不用担心预分配和惰性分配造成的浪费
SDS的优点
1.比起C字符串,SDS获取字符串长度是O(1)
2.比起C字符串,不会缓冲区溢出
3.减少字符串扩容时的分配次数
4.二进制安全
5.兼容C字符串,可以重用C字符串的函数库