redis 没有直接使用c语言传统的字符串表示,而是自己构建了简单动态字符串(SDS)的抽象类型,并将SDS用作redis的默认字符串表示
redis的数据库里面,包含字符串值的键值对在底层都是SDS实现的
执行 rpush fruits "apple" "banana" "pits"
那么redis将在数据库中创建一个新的键值对,其中:
1、键值对的键是一个字符串对象,对象的底层实现是一个保存了字符串的fruits的SDS
2、键值对的值是一个列表对象,列表对象包含了三个字符串对象,这三个字符串对象分别由撒呢SDS实现
SDS结构:
free属性:表示这个SDS没有分配任何未使用的空间
len:保存字符串的长度
buf:字符串的值是一个char类型的数组,最后一个字符串则保存了空字符串‘\0‘
SDS遵循c字符串以空字符串结尾的惯例,保存空字符的1字节空间不计算在SDS的len属性里,并且为空字符串分配额外的1字节空间
为啥遵循空字符串结尾的惯例呢?
因为SDS可以只用重用一部分C字符串函数库中的函数
SDS比C字符串更适用redis的原因是啥呢?
1、因为c字符不记录自身的长度,所以要获取长度,必须遍历整个字符串,而对于SDS来说,设置和更新SDS长度的工作是由SDS的API在执行时自动完成,使用SDS无须进行任何手动修改长度的工作,这确保了redis获取字符串长度工作不会成为redis的性能瓶颈
2、c字符串因为不记录自身长度带来的另一个问题是易造成缓冲区溢出,与c字符串不同的是,SDS的空间分配策略完全杜绝了发生缓冲区溢出的可能性,当SDS API需要对SDS进行修改时,API会先检查SDS的空间是否满足修改所需的要求,如果不满足的话,API会自动将SDS的空间扩展至执行修改所需的大小,然后才执行时机的修改操作,使用使用SDS既不需要手动修改SDS的空间大小,也不会出现前面所说的缓冲区溢出问题。
3、c字符串在修改时比如ajppend操作,如果程序忘记通过内存重分配来扩展底层数组的空间大小就会产生缓冲区溢出。如果进行缩短字符串操作,比如trim,如果程序忘记通过内存重分配来释放字符串不再使用的部分,就会产品内存泄露,为了解决这两个问题,SDS通过未使用空间解除了字符串长度和底层数组长度之间的管理,通过未使用空间,SDS实现了空间预分配和惰性空间释放两种优化策略。
详细说一下这两种优化策略
空间预分配:用于优化SDS的字符串增长操作,当SDS的api对一个sds进行修改,并且需求对sds进行空间扩展的时候,程序不仅会未sds分配修改锁必须要的空间,还会为sdd分配额外的未使用空间,其中公式是:
如果对sds进行修改之后,sds的长度<1MB,那么程序分配和len属性同样大小的未使用空间
如果对sds进行修改好,sds的长度>=1MB,那么程序会分配1MB的未使用空间
使用空间预分配策略,redis可以减少连续执行字符串操作所需的内存重分配次数
惰性空间释放:用户优化sds的字符串缩短的操作,通过惰性空间释放策略,sds避免了缩短字符串时所需要的内存重分配操作,并为将来可能有的增长操作提供了优化。
以上就是SDs比c字符串更使用redis的原因。
为了确保redis可以适用于各种不同的使用场景,sds的api都是二进制安全的,所有的SDs API都会以处理二进制的方式来处理SDS存放在buf数组中数据
通过使用二进制安全的SDS,而不是C字符串,使得redis不仅可以保存文本数据还可以保存任意格式的二进制数据。
重点回顾
1、redis只会使用c字符串最为字面量,在大多数情况下,redis使用sds作为字符串的表示
2、比如c字符串,sds更具有以下优点
1)常数负责度获取字符串长度
2)杜绝缓冲区溢出
3)减少修改字符串长度时所需要的内存重分配次数
4)二进制安全
5)兼容部分c字符函数
之前被问 redis string最大长度 接下来将着重看redis底层 加油
原文地址:https://www.cnblogs.com/weiluoyan/p/9026140.html