SDS (Simple Dynamic String,简单动态字符串)是 Redis 底层所使用的字符串表示, 几乎所有的 Redis 模块中都用了 sds。
本文将对 sds 的实现、性能和功能等方面进行介绍
1. redis中的string
先执行几个命令
可以看到:
- 当设置是整数,底层编码则采用int。
- 当字符串长度小于等于44,底层编码采用embstr;
- 当字符串长度大于44,底层编码采用raw;
embstr
编码是专门用于保存短字符串的一种优化编码方式, 这种编码和 raw
编码一样, 都使用 redisObject
结构和 sdshdr
结构来表示字符串对象,
但 raw
编码会调用两次内存分配函数来分别创建 redisObject
结构和 sdshdr
结构, 而 embstr
编码则通过调用一次内存分配函数来分配一块连续的空间, 空间中依次含 redisObject
和 sdshdr
两个结构。
详细对比参考文章 字符串对象 — Redis 设计与实现
2. 为什么需要sds字符串?
- O(1)获取长度,c语言的字符串本身不记录长度,而是通过末尾的
\0
作为结束标志,而sds本身记录了字符串的长度所以获取直接变为O(1)的时间复杂度、同时,长度的维护操作由sds的本身api实现 - 防止缓冲区溢出bufferoverflow:由于c不记录字符串长度,相邻字符串容易发生缓存溢出。sds在进行添加之前会检查长度是否足够,并且不足够会自动根据api扩容
- 减少字符串修改的内存分配次数:使用动态扩容的机制,根据字符串的大小选择合适的header类型存储并且根据实际情况动态扩展。
- 使用空间预分配和惰性空间释放,其实就是在扩容的时候,根据大小额外扩容2倍或者1M的空间,方面字符串修改的时候进行伸缩
- 使用二进制保护,数据的读写不受特殊的限制,写入的时候什么样读取就是什么样
- 支持兼容部分的c字符串函数,可以减少部分API的开发
优点:
空间预分配:当一个SDS被修改成更长的buf时,除了会申请本身需要的内存外,还会额外申请一些空间。
惰性空间释放:当一个SDS被修改成更短的buf时,并不会把多余的内存还回去,而是会保存起来。
3. SDS的实现
4. 总结
这种设计的核心思想就是空间换时间,只要free还剩余足够的空间时,下次String变长的时候,不会像系统申请内存。
类似 Python中list的内存管理方式, 本质都是以浪费一些空间为代价,预申请额外的内存,从而减少申请内存的次数。