这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战
前言
又是一年面试季节,反正不管你只初级,中级,还是高级,面试的时候,先各种实现原理来一套,其中redis的实现原理被问的尤其的多。
虽然咱们平常大家都是crud boy,可是如果在面试的时候,可以通过把底层原理实现一顿输出,保证可以洪得面试官一楞楞的,那么薪资翻倍也就不是梦了。
今天咱们就来聊一下,redis中最简单也是使用频率最高的string,是怎么实现的。
redis中的动态字符串
我们通常知道,很多语言的string类型,其实使用的是传统的C语言的字符串表示,但是redis不一样,它自己创建了一个叫做简单动态字符串的一个数据类型,也就是SDS。
SDS的一个结构也非常的简单
type SDS struct {
len int,
free int,
char []buf
}
这里我们看一个例子更好理解
从这个例子我们可以看出:
free为0,意味着这个SDS没有分配任何未使用的空间。
len为5,意味着这个SDS保存了一个五字节长度的字符串。
buf属性则是指向了一个char类型的一个数组,数组存储了redis这五个字母,同时有一个休止符。
SDS和C字符串的区别
从上面实现我们可以看出,其实buf上的存储跟C字符的实现非常的类似,那么为什么redis又要采用这样的一种新的实现方式呢?
常数级别的获取字符串长度
想要获取自身长度的时候,C字符是不具备这个长度信息的,也就是说,对于C字符来说,它必须通过遍历的方式,当遇到休止符的时候,就截止,也就是复杂度会是n。
但是对于SDS来说,因为它本身结构里面存储了长度信息,所以,对于获取自身长度,它并不需要遍历的方式,而是可以直接获得,也就是复杂度为1。
缓冲区的溢出
对于C字符来说,它不记录自身的长度,所以在初始化的时候,假定已经分配了足够多的内存空间来存储字符,可是如果假定不成立的时候,就会出现缓冲区溢出的情况,当这种情况发生的时候,就会造成字符保存不完整的情况出现。
但是对于SDS来说就完全不存在这样的一个问题,因为在执行拼接操作的时候,它会去检查自身的空间是否足够,如果不足的时候,会进行扩容操作,所以也就不会有一个缓冲区溢出的情况出现了。
减少修改字符的时候的内存重分配
前面有提到,就是C字符是不记录自身长度的,所以每次对底层的字符长的数组进行修改的时候,都需要做一次内存的重新分配,这样对于性能的开销是非常巨大的。
但是对于SDS来说,就没有这个问题出现
空间预分配
首先,在扩展存储空间的时候,SDS不仅会修改必要的空间,还会为SDS分配一部分未使用的空间,也就是我们看到的free,这样就可以避免每次对字符集进行修改都会进行一次空间的重新分配的问题。
惰性空间释放
同样的,当对一个本身长字符集进行减少操作的时候,SDS也不是直接把那些空间就给回收了,而是会放到free里面去,方便下一次的使用,这样做的好处就是减少了内存重新分欸的问题。
总结
总的来说,SDS的实现其实不复杂,同时也因为它兼容着部分的C字符串的函数,所以,它才被用作redis的一个string的底层实现。