Redis的设计与实现(1)-SDS简单动态字符串
现在在高铁上, 赶着春节回家过年, 无座站票, 电脑只能放行李架上, 面对着行李架撸键盘--看过<Redis的设计与实现>这本书, 突然想起, 便整理下SDS的内容, 相对后面的章节, 算是比较简单的~
大多数情况下, Redis使用SDS(Simple Dynamic String, 简单动态字符串)作为字符串表示, 比起C字符串, SDS具有以下优点:
- 常数复杂度获取字符串长度;
- 杜绝缓冲区溢出;
- 减少修改字符串时带来的内存重分配次数;
- 二进制安全;
- 兼容部分C字符串函数.
以上列举的优点, 也算是这篇文章的主要内容. 先从定义说起吧:
1. SDS的定义
SDS结构定义如下(sds.h/sdshdr):
struct sdshdr {
// 记录buf数组中已使用字节的数量, 等于SDS所保存字符串的长度
int len;
// 记录buf数组中未使用字节的数量
int free;
// 字节数组, 用于保存字符串
char buf[];
}
- len属性记录了已使用的字节数量(字符串长度);
- free属性的值为0, 表示这个SDS没有未使用的空间;
- free属性的值为5, 表示这个SDS保存了一个5字节长的字符串;
- buf属性是一个char类型的数组, 数组的最后一个字节保存了空字符\0.
SDS遵循C字符串以空字符串结尾的惯例, 空字符不计入SDS的len属性, 即额外为空字符分配了1字节的空间, 并且添加空字符到字符串末尾均由SDS函数自动完成, 对使用者完全透明. 该特性带来的好处是, SDS可以直接复用C字符串函数库的部分函数.
2. SDS与C字符串的区别
2.1 常数复杂度获取字符串长度
- 由于C字符串不记录自身长度, 所以获取长度时需要遍历整个字符串, 直到遇到空字符\0为止, 该操作的复杂度为O(N);
- 由于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分配额外的未使用空间.
额外分配的未使用空间数量, 由以下公式决定:
- 如果对SDS进行修改之后, SDS的长度(即len属性)将小于1MB, 那么程序分配和len属性同样大小的未使用空间, 这时SDS的len属性的值将和free属性的值相同: 比如修改之后, SDS的len将变成13字节, 那么程序也会分配13字节的未使用空间, buf长度将变成 13Byte + 13Byte + 1Byte = 27Byte;
- 如果对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具有以下优点:
- 常熟复杂度获取字符串长度;
- 杜绝缓冲区溢出;
- 减少修改字符串长度时所需的内存重分配次数;
- 二进制安全;
- 兼容部分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简单动态字符串的更多相关文章
- Redis数据类型之SDS简单动态字符串
一,简单的动态字符串 1,Redis自己构建了一种名为简单动态字符串的抽象类型,并将SDS用作Redis的默认字符串表示, 2,在redis的数据库里面,包含字符串值的键值对在底层都是由SDS实现的 ...
- 小白的Redis学习(一)-SDS简单动态字符串
本文为读<Redis设计与实现>的记录.该书以Redis2.9讲解Redis相关内容.请注意版本差异. Redis使用C语言实现,他对C语言中的char类型数据进行封装,构建了一种简单动态 ...
- Redis底层探秘(一):简单动态字符串(SDS)
redis是我们使用非常多的一种缓存技术,他的性能极高,读的速度是110000次/s,写的速度是81000次/s.这么高的性能背后,到底是怎么样的实现在支撑,这个系列的文章,我们一起去看看. redi ...
- Redis源码之SDS简单动态字符串
Redis 是内存数据库,高效使用内存对 Redis 的实现来说非常重要. 看一下,Redis 中针对字符串结构针对内存使用效率做的设计优化. 一.SDS的结构 c语言没有string类型,本质是ch ...
- Redis设计与实现读书笔记——简单动态字符串
前言 项目里用到了redis数据结构,不想只是简单的调用api,这里对我的读书笔记做一下记录.原文地址: http://www.redisbook.com/en/latest/internal-dat ...
- 关于redis中SDS简单动态字符串
1.SDS 定义 在C语言中,字符串是以’\0’字符结尾(NULL结束符)的字符数组来存储的,通常表达为字符指针的形式(char *).它不允许字节0出现在字符串中间,因此,它不能用来存储任意的二进制 ...
- 【笔记】《Redis设计与实现》chapter2 简单动态字符串
------------恢复内容开始------------ 2.1 SDS的定义 struct sdshdr{ // 记录buf数组中已使用字节的数量 // 等于SDS所保存字符串的长度(不含'\0 ...
- sds(简单动态字符串) 内存预分配优化策略
* 1024 , 也就是说. 当大小小于 1MB 的字符串运行追加操作时,sdsMakeRoomFor 就为它们分配多于所需大小一倍的空间: 当字符串的大小大于 1MB . 那么 sdsMakeRoo ...
- Redis的简单动态字符串实现
Redis 没有直接使用 C 语言传统的字符串表示(以空字符结尾的字符数组,以下简称 C 字符串), 而是自己构建了一种名为简单动态字符串(simple dynamic string,sds)的抽象类 ...
- 图解Redis之数据结构篇——简单动态字符串SDS
图解Redis之数据结构篇--简单动态字符串SDS 前言 相信用过Redis的人都知道,Redis提供了一个逻辑上的对象系统构建了一个键值对数据库以供客户端用户使用.这个对象系统包括字符串对象 ...
随机推荐
- Go语言入门实战: 猜谜游戏+在线词典
包含基础语法和入门Go语言的3个案例 速览基础语法 对于耳熟能详的顺序结构.分支结构(if else-if else.switch).循环结构(for)不作赘述. 数组: 长度固定的元素序列 pack ...
- BAT 基础语法
命令 //功能 echo //标准输出命令 在CMD窗口中 显示echo 后的内容 @ //关闭当前行的 回显 回显:源代码在 CMD 窗口中再次显示 pasue // 暂停程序 的执行 ...
- LeetCode 周赛 342(2023/04/23)容斥原理、计数排序、滑动窗口、子数组 GCB
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 前天刚举办 2023 年力扣杯个人 SOLO 赛,昨天周赛就出了一场 Easy - Ea ...
- es6的Symbol数据类型
ES6引入了一种新的原始数据类型Symbol,表示独一无二的值.它是JavaScript语言的第七种数据类型,前六种是:Undefined.Null.布尔值(Boolean).字符串(String). ...
- Python获取jsonp数据
使用python爬取数据时,有时候会遇到jsonp的数据格式,由于不是json的,所以不能直接使用json.loads()方法来解析,需要先将其转换为json格式,再进行解析.在前面讲了jsonp的原 ...
- SVN添加新的用户
1.首先确定svn的文件夹的位置 find / -name svn 2.通过find找到svn地址之后,cd进入 /var/svn/ 下 cd /var/svn/ 3.修改/var/svn/下面的au ...
- 爬虫之浏览器指纹ja3_hash的更改
浏览器指纹 反爬中会遇到浏览器指纹,它是不会随着你更换 IP 或者 User-Agent 而改变的.并且他们的指纹每次请求也是固定的.只要网站发现某个拥有特定指纹的客户端持续高频率请求网站,它就可以把 ...
- 2022-12-12:有n个城市,城市从0到n-1进行编号。小美最初住在k号城市中 在接下来的m天里,小美每天会收到一个任务 她可以选择完成当天的任务或者放弃该任务 第i天的任务需要在ci号城市完成,
2022-12-12:有n个城市,城市从0到n-1进行编号.小美最初住在k号城市中 在接下来的m天里,小美每天会收到一个任务 她可以选择完成当天的任务或者放弃该任务 第i天的任务需要在ci号城市完成, ...
- 2020-10-01:谈谈golang的空结构体。
福哥答案2020-10-01:#福大大架构师每日一题# 1.map.value是空结构体,构造集合. 2.通道.只传递信号,不传递数据. 3.切片.不管切片多长,都不会占用空间. 4.仅包含方法的结构 ...
- 2022-01-20: 矩形区域不超过 K 的最大数值和。 给你一个 m x n 的矩阵 matrix 和一个整数 k ,找出并返回矩阵内部矩形区域的不超过 k 的最大数值和。 题目数据保证总会存在一
2022-01-20: 矩形区域不超过 K 的最大数值和. 给你一个 m x n 的矩阵 matrix 和一个整数 k ,找出并返回矩阵内部矩形区域的不超过 k 的最大数值和. 题目数据保证总会存在一 ...