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提供了一个逻辑上的对象系统构建了一个键值对数据库以供客户端用户使用.这个对象系统包括字符串对象 ...
随机推荐
- GitLab 配置优化
代码库最近换了,使用了 GitLab.这两天观察了下服务器的使用情况,发现 GitLab 很吃内存.直接占用了 30 多个G的内存. 一.发现问题 使用 top 命令查看内存时,发现前几十个都是同一个 ...
- 06-打包html资源
/** * loader:1. 下载 2. 使用(配置loader) * plugins:1. 下载 2. 引入 3. 使用 */ const { resolve } = require('path' ...
- 05-打包样式资源(编写webpack配置文件)
/** * webpack.config.js webpack的配置文件 * 作用:指示 webpack 干哪些活(当你运行 webpack 指令时,会加载里面的配置) * * 所有构件工具都是基于n ...
- SqlServer查看表结构
SELECT CASE WHEN col.colorder = 1 THEN obj.name ELSE '' END AS 表名 ,CASE WHEN col.colorder=1 then isn ...
- 自定义Python版本ESL库访问FreeSWITCH
环境:CentOS 7.6_x64Python版本:3.9.12FreeSWITCH版本 :1.10.9 一.背景描述 ESL库是FreeSWITCH对外提供的接口,使用起来很方便,但该库是基于C语言 ...
- “结巴”中文分词:做最好的 Python 中文分词组件
jieba "结巴"中文分词:做最好的 Python 中文分词组件 "Jieba" (Chinese for "to stutter") C ...
- Django 有关 models 数据类型介绍:
在model中添加字段的格式一般为: field_name = field_type(**field_options) 一 field options(所有字段共用) 1 null 默认为F ...
- iOS网络数据指标收集
在平时开发中有时候需要收集网络不同阶段性能数据来分析网络情况,下面总结了2种收集方式. 1.通过NSURLSession提供的代理方法收集 2.通过NSURLProtocol做统一网络请求拦截收集 通 ...
- 基于Jmeter+ant+Jenkins+钉钉机器人群通知的接口自动化测试
前言 搭建jmeter+ant+jenkins环境有些前提条件,那就是要先配置好java环境,本地java环境至少是JDK8及以上版本,最好是JAVA11或者JAVA17等较高的java环境,像jen ...
- Win32 GUI 汇编
获取句柄 API函数 GetModuleHandle 取模块句柄,lpModuleName 是一个指向模块名称字符串的指针,使用 NULL 获取当前程序句柄. invoke GetModuleHand ...