现在在高铁上, 赶着春节回家过年, 无座站票, 电脑只能放行李架上, 面对着行李架撸键盘--看过<Redis的设计与实现>这本书, 突然想起, 便整理下SDS的内容, 相对后面的章节, 算是比较简单的~

大多数情况下, Redis使用SDS(Simple Dynamic String, 简单动态字符串)作为字符串表示, 比起C字符串, SDS具有以下优点:

  1. 常数复杂度获取字符串长度;
  2. 杜绝缓冲区溢出;
  3. 减少修改字符串时带来的内存重分配次数;
  4. 二进制安全;
  5. 兼容部分C字符串函数.

以上列举的优点, 也算是这篇文章的主要内容. 先从定义说起吧:

1. SDS的定义

SDS结构定义如下(sds.h/sdshdr):

struct sdshdr {
// 记录buf数组中已使用字节的数量, 等于SDS所保存字符串的长度
int len;
// 记录buf数组中未使用字节的数量
int free;
// 字节数组, 用于保存字符串
char buf[];
}
  1. len属性记录了已使用的字节数量(字符串长度);
  2. free属性的值为0, 表示这个SDS没有未使用的空间;
  3. free属性的值为5, 表示这个SDS保存了一个5字节长的字符串;
  4. buf属性是一个char类型的数组, 数组的最后一个字节保存了空字符\0.

SDS遵循C字符串以空字符串结尾的惯例, 空字符不计入SDS的len属性, 即额外为空字符分配了1字节的空间, 并且添加空字符到字符串末尾均由SDS函数自动完成, 对使用者完全透明. 该特性带来的好处是, SDS可以直接复用C字符串函数库的部分函数.

2. SDS与C字符串的区别

2.1 常数复杂度获取字符串长度

  1. 由于C字符串不记录自身长度, 所以获取长度时需要遍历整个字符串, 直到遇到空字符\0为止, 该操作的复杂度为O(N);
  2. 由于SDS在len属性中记录了SDS本身的长度, 所以获取一个SDS的长度的复杂度为O(1).

Redis使用SDS, 将获取字符串长度所需的复杂度从O(N)降低到O(1), 确保获取字符串长度的工作不会成为Redis的性能瓶颈.

2.2 杜绝缓冲区溢出

由于C字符串不记录自身长度, 以函数strcat来说, 当执行该函数时, 都是认为已经为dest分配了足够的内存容纳src字符串, 但如果该假设不成立, 就会产生缓冲区溢出.

然而, SDS的字符串空间分配策略, 从根本上杜绝了缓冲区溢出的可能性: 当SDS-API需要对SDS进行修改时, API会先检查SDS的空间是否满足修改所需的需求, 如果不满足的话, API会自动扩容至所需大小, 再执行修改操作. 所以, SDS无需手工维护SDS的空间大小, 也不会产生缓冲区溢出的问题.

2.3 减少修改字符串时带来的内存重分配次数

由于C字符串不记录自身长度, 所以每次增长或缩减字符串, 需要对保存这个C字符串的数组进行一次内存重分配操作:

1.如果程序执行的是增长字符串的操作, 比如拼接操作append, 需要进行内存重分配操作, 扩展底层数组至合适大小, 否则将会产生缓冲区溢出;

2.如果程序执行的是缩短字符串的操作, 比如截断操作trim, 需要进行内存释放操作, 否则将会产生内存泄露.

Redis作为数据库, 经常用于速度要求严苛, 数据被频繁修改的场合, 减少内存的重分配次数能提高性能. SDS通过未使用空间解除了字符串长度底层数组长度之间的关联: 在SDS中, buf数组的长度不一定就是字符数加一, 数组里面可以包含未使用的字节, 而这些字节的数量就由SDS的free属性记录.

通过未使用空间, SDS实现了空间预分配和惰性空间释放两种优化策略.

1.空间预分配

当SDS-API对SDS进行修改, 并且需要对SDS进行空间扩展的时候, 程序不仅会为SDS分配修改所需的空间, 还会为SDS分配额外的未使用空间.

额外分配的未使用空间数量, 由以下公式决定:

  1. 如果对SDS进行修改之后, SDS的长度(即len属性)将小于1MB, 那么程序分配和len属性同样大小的未使用空间, 这时SDS的len属性的值将和free属性的值相同: 比如修改之后, SDS的len将变成13字节, 那么程序也会分配13字节的未使用空间, buf长度将变成 13Byte + 13Byte + 1Byte = 27Byte;
  2. 如果对SDS进行修改之后, SDS的长度将大于等于1MB, 那么程序会分配1MB的未使用空间: 比如修改之后, SDS的len将变成30MB, 那么程序会分配1MB的未使用空间, buf长度将变成 30MB + 1MB + 1Byte.

通过空间预分配策略, Redis可以减少连续执行字符串增长操作所需的内存重分配次数.

2.惰性空间释放

惰性空间释放用于优化SDS的字符串缩短操作: 当SDS-API需要缩短SDS字符串时, 程序并不会立即回收内存, 而是使用free属性将这些字节的数量记录起来, 并等待将来使用.

当然, SDS也提供了相应的API真正地释放SDS的未使用空间, 无需担心该策略带来的内存浪费问题.

2.4 二进制安全

C字符串除了末尾, 不能包含空字符, 否则最先被程序读入的空字符将被误认为是字符串结尾, 所以C字符串只能保存文本数据, 而不能保存像图片, 音频, 视频, 压缩文件这样的二进制数据.

Redis为了可以适用于各种不同的场景, SDS-API都是二进制安全的(binary-safe), 所有SDS-API都会以二进制的方式处理buf数组里面的数据, 不限制, 不过滤, 不假设, 写入什么就读到什么.

2.5 兼容部分C字符串函数

虽然SDS-API是二进制安全的, 但它们一样遵循C字符串以空字符结尾的惯例: 这些API总是将SDS保存的数据末尾数组为空字符, 并且总会在为buf分配空间时多分配一个字节来容纳这个空字符, 以复用<string.h>库定义的函数.

3. SDS的API

函数 作用 复杂度
sdsnew 创建一个包含给定C字符串的SDS O(N), N为给定C字符串的长度
sdsempty 创建一个不包含任何内容的空SDS O(1)
sdsfree 释放给定的SDS O(N), N为被释放SDS的长度
sdslen 返回SDS的已使用空间字节数 这个值可以通过读取SDS的len属性来直接获得, 复杂度为O(1)
sdsavail 返回SDS的未使用空间字节数 这个值可以通过读取SDS的free属性来直接获得, 复杂度为O(1)
sdsdup 创建一个给定SDS的副本(copy) O(N), N为给定C字符串的长度
sdsclear 清空SDS保存的字符串内容 因为惰性空间释放策略, 复杂度为O(1)
sdscat 将给定C字符串拼接到另一个SDS字符串的末尾 O(N), N为被拼接C字符串的长度
sdscatsds 将给定SDS字符串拼接到另一个SDS字符串的末尾 O(N), N为被拼接SDS字符串的长度
sdscpy 将给定的C字符串复制到SDS里面, 覆盖SDS原有的字符串 O(N), N为被复制SDS字符串的长度
sdsgrowzero 用空字符将SDS扩展至给定长度 O(N), N为扩展新增的字节数
sdsrange 保留SDS给定区间内的数据, 不在区间内的数据会被覆盖或清除 O(N), N为被保留数据的字节数
sdstrim 接受一个SDS和一个C字符串作为参数, 从SDS中移除所有在C字符串中出现过的字符 O(N^2), N为给定C字符串的长度
sdscmp 对比两个SDS字符串是否相同 O(N), N为两个SDS钟较短的那个SDS的长度

4. 总结

  • Redis在大多数情况下使用SDS作为字符串的表示;

  • 相比C字符串, SDS具有以下优点:

  1. 常熟复杂度获取字符串长度;
  2. 杜绝缓冲区溢出;
  3. 减少修改字符串长度时所需的内存重分配次数;
  4. 二进制安全;
  5. 兼容部分C字符串函数.
  • C字符串和SDS之间的区别:
C字符串 SDS
获取字符串长度的复杂度为O(N) 获取字符串长度的复杂度为O(1)
API是不安全的,可能造成缓冲区溢出 API是安全的,不会造成缓冲区溢出
修改字符串长度N次必然需要执行N次内存重分配 修改字符串长度N次最多需啊哟执行N次内存重分配
可以使用所有<string.h>库中的函数 可以使用一部分<string.h>库中的函数

以上笔记都是整理自<Redis的设计与实现>, 真的很感谢作者, 也希望自己能坚持写下去_.


文章来源于本人博客,发布于 2018-02-15,原文链接:https://imlht.com/archives/106/

Redis的设计与实现(1)-SDS简单动态字符串的更多相关文章

  1. Redis数据类型之SDS简单动态字符串

    一,简单的动态字符串 1,Redis自己构建了一种名为简单动态字符串的抽象类型,并将SDS用作Redis的默认字符串表示, 2,在redis的数据库里面,包含字符串值的键值对在底层都是由SDS实现的 ...

  2. 小白的Redis学习(一)-SDS简单动态字符串

    本文为读<Redis设计与实现>的记录.该书以Redis2.9讲解Redis相关内容.请注意版本差异. Redis使用C语言实现,他对C语言中的char类型数据进行封装,构建了一种简单动态 ...

  3. Redis底层探秘(一):简单动态字符串(SDS)

    redis是我们使用非常多的一种缓存技术,他的性能极高,读的速度是110000次/s,写的速度是81000次/s.这么高的性能背后,到底是怎么样的实现在支撑,这个系列的文章,我们一起去看看. redi ...

  4. Redis源码之SDS简单动态字符串

    Redis 是内存数据库,高效使用内存对 Redis 的实现来说非常重要. 看一下,Redis 中针对字符串结构针对内存使用效率做的设计优化. 一.SDS的结构 c语言没有string类型,本质是ch ...

  5. Redis设计与实现读书笔记——简单动态字符串

    前言 项目里用到了redis数据结构,不想只是简单的调用api,这里对我的读书笔记做一下记录.原文地址: http://www.redisbook.com/en/latest/internal-dat ...

  6. 关于redis中SDS简单动态字符串

    1.SDS 定义 在C语言中,字符串是以’\0’字符结尾(NULL结束符)的字符数组来存储的,通常表达为字符指针的形式(char *).它不允许字节0出现在字符串中间,因此,它不能用来存储任意的二进制 ...

  7. 【笔记】《Redis设计与实现》chapter2 简单动态字符串

    ------------恢复内容开始------------ 2.1 SDS的定义 struct sdshdr{ // 记录buf数组中已使用字节的数量 // 等于SDS所保存字符串的长度(不含'\0 ...

  8. sds(简单动态字符串) 内存预分配优化策略

    * 1024 , 也就是说. 当大小小于 1MB 的字符串运行追加操作时,sdsMakeRoomFor 就为它们分配多于所需大小一倍的空间: 当字符串的大小大于 1MB . 那么 sdsMakeRoo ...

  9. Redis的简单动态字符串实现

    Redis 没有直接使用 C 语言传统的字符串表示(以空字符结尾的字符数组,以下简称 C 字符串), 而是自己构建了一种名为简单动态字符串(simple dynamic string,sds)的抽象类 ...

  10. 图解Redis之数据结构篇——简单动态字符串SDS

    图解Redis之数据结构篇--简单动态字符串SDS 前言     相信用过Redis的人都知道,Redis提供了一个逻辑上的对象系统构建了一个键值对数据库以供客户端用户使用.这个对象系统包括字符串对象 ...

随机推荐

  1. 【FAQ】关于华为推送服务因营销消息频次管控导致服务通讯类消息下发失败的解决方案

    一. 问题描述 使用华为推送服务下发IM消息时,下发消息请求成功且code码为80000000,但是手机总是收不到消息: 在华为推送自助分析(Beta)平台查看发现,消息发送触发了频控. 二. 问题原 ...

  2. Prism Sample 22-ConfirmCancelNavigation

    导航到一个视图,如果在离开这个视图时需要确认,在VM中实现以下接口 public class ViewAViewModel : BindableBase, IConfirmNavigationRequ ...

  3. thinkphp常量定义

    是已经封装好的系统常量 主要是用在控制器下面的动作当中 这样能很大的提高我们的开发效率主要有下面的一些     手册上面都有的     __ROOT__ 网站的根目录     __APP__ 代表项目 ...

  4. 跑在笔记本里的大语言模型 - GPT4All

    何为GPT4All GPT4All 官网给自己的定义是:一款免费使用.本地运行.隐私感知的聊天机器人,无需GPU或互联网. 从官网可以得知其主要特点是: 本地运行(可包装成自主知识产权) 无需GPU( ...

  5. python库Munch的使用记录

    开头 日常操作字典发现发现并不是很便利,特别是需要用很多 get('xxx','-') 的使用,就觉得很烦,偶然看到Kuls大佬公众号发布的一篇技术文有对 python munch库的使用, 使得字典 ...

  6. sklearn中的KFold简单介绍

    这一部分主要讲解关于什么是K-foldCV(K折交叉验证),简单的使用一些案例进行分析,然后使用sklearn库函数中一些简单的案例进行分析. 在机器学习中,多数最主要的功能函数被封装到sklearn ...

  7. 2021-06-28:最接近目标值的子序列和。给你一个整数数组 nums 和一个目标值 goal 。你需要从 nums 中选出一个子序列,使子序列元素总和最接近 goal 。也就是说,如果子序列元素和

    2021-06-28:最接近目标值的子序列和.给你一个整数数组 nums 和一个目标值 goal .你需要从 nums 中选出一个子序列,使子序列元素总和最接近 goal .也就是说,如果子序列元素和 ...

  8. 初等数论——素数,逆元,EXGCD有关

    初等数论 素数定义 设整数 \(p\ne 0,\pm 1\) .如果 \(p\) 除了平凡约数以外没有其他约数,那么称 \(p\) 为素数(不可约数). 若整数 \(a\ne 0,\pm 1\) 且 ...

  9. cv学习总结(SVM,softmax)10.24-10.30

    本周完成了SVM课程笔记的阅读,包括SVM的基本原理以及SVM的优化过程,以及实现了SVM的两种损失函数(svm以及softmax)的线性分类器,以及学习了反向传播以及神经网络的初步.其中:svm在测 ...

  10. 每周更新 | Verilog测试用例及波形展示图功能上线

    Hi,亲爱的技术伙伴,经过产研团队的努力,本周ShowMeBug有以下4个功能上线啦- 芯片语言 Verilog 支持测试用例 芯片语言 Verilog 支持测试用例,自动评分同步上线- 同时,Ver ...