Redis核心数据结构之SDS(二)

SDS与C字符串的区别

杜绝缓冲区溢出

除了获取字符串长度的复杂度高之外,C字符串不记录自身长度带来的另一个问题是
容易造成缓冲区溢出(buffer overflow).例如<string.h>/strcat函数可以将src字符串
中的内容拼接到dest字符串的末尾:

char *strcat(char *dest,const char *src);

因为C字符串不记录自身的长度,所以strcat假定用户在执行这个函数时,已经为dest
分配了足够多的内存,可以容纳src字符串中的所有内容,而一旦这个假定不成立时,
就会产生缓冲区溢出。
与C字符串不同,SDS的空间分配策略完全杜绝了发生缓冲区溢出的可能性:当SDS API
需要对SDS进行修改时,API会先检查SDS的空间是否满足所需的要求,如果不满足的
话,API会自动将SDS的空间扩展至执行修改所需的大小,然后才执行实际的修改操作,
所以使用SDS既不需要手动修改SDS的空间大小,也不会出现前面所说的缓冲区溢出问题

例子

例如,假设程序里有两个在内存中紧邻着的C字符串s1和s2,
其中s1保存了字符串"Redis",而s2则保存了字符串"MongoDB",如图所示
在这里插入图片描述
如果一个程序决定通过执行:

strcat(s1, " Cluster");

将S1的内容修改为"Redis Cluster",但粗心地却忘了在执行strcat之前为s1
分配足够的空间,那么在strcat函数执行之后,S1的数据将溢出到了S2所在
的空间中,导致S2保存的内容被意外地修改,如图所示
在这里插入图片描述
举个例子,SDS的API里面也有一个用于执行拼接操作的sdscat函数,它可以
将一个C字符串拼接到给定SDS所保存的字符串的后面,但是在执行拼接操作之前,
sdscat会先检查给定SDS的空间是否足够,如果不够的话,sdscat就会先扩展SDS的
空间,然后才执行拼接操作,例如执行,sdscat(s, " Cluster"),SDS值如图所示,
那么sdscat将在执行拼接操作之前检查s的长度是否足够,在发现s目前的空间不足以
拼接" Cluster"之后,sdscat就会先扩展s的空间,然后再执行拼接" Cluster"的操作,
在这里插入图片描述
拼接之后的SDS结构如图.
注意,sdscat不仅对这个SDS进行了拼接操作,它还为SDS分配了13字节的未使用空间,
并且拼接之后的字符串也正好是13字节长,这种现象既不是bug也不是巧合,它和SDS的
空间分配策略有关
在这里插入图片描述

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

因为C字符串并不记录自身的长度,所以对于一个包含了N个字符的C字符串来说,这个C字符串的底层实现总是一个N+1个字符长的数组(额外的一个字符空间用于保存空字符)。因为C字符串的长度和底层数组的长度之间存在着这种关联性,所以每次增长或者缩短一个C字符串,程序都总要对保存这个C字符串的数组进行一次内存重分配操作:

  • 1.如果程序执行的是增长字符串的操作,比如拼接操作(append),那么再执行这个操作之前,程序需要先通过内存重分配来扩展底层数组的空间大小——如果忘了这一步就会产生缓冲区溢出
  • 2.如果程序执行的是缩短字符串的操作,比如截断操作(trim),那么在执行这个操作之后,程序需要通过内存重分配来释放字符串不再使用的那部分空间——如果忘了这一步就会产生内存泄漏

例子

  • 例如,如果我们持有一个值为"Redis"的C字符串s,那么为了将s的值改为"Redis Cluster",在执行:
strcat(s," Cluster");

之前,我们需要先使用内存重分配操作,扩展s的空间,之后,
如果我们又打算将s的值从"Redis Cluster"改为"Redis Cluster Tutorial",那么再执行:

strcat(s, " Tutorial");

之前,我们需要再次使用内存重分配扩展s的空间,注入此类。
因为内存重分配设计复杂的算法,并且可能需要执行系统调用,所以它通常是一个比较耗时的操作:

  • 1.在一般程序中,如果修改字符串长度的情况不太常出现,那么每次修改都执行一次内存重分配也是可以接受的
  • 2.但是Redis作为数据库,经常被用于速度要求严苛、数据被频繁修改的场合,如果每次修改字符串的长度都需要执行一次内存重分配的话,那么光是执行内存重分配的时间就会占去修改字符串所用时间的一大部分,如果这种修改频繁地发生的话,可能还会对性能造成影响

空间预分配和惰性空间释放

为了避免C字符串的这种缺陷,SDS通过未使用空间接触了字符串长度和底层数组长度之间的关联:在SDS中,buf数组的长度不一定就是字符数量加一,数组里面可以包含未使用的字节,而这些字节的数量就由SDS的free属性记录.通过未使用空间,SDS实现了空间预分配和惰性空间释放两种优化策略

空间预分配

空间预分配用于优化SDS的字符串增长操作:当SDS的API对一个SDS进行修改,并且需要对SDS进行空间扩展的时候,程序不仅会为SDS分配修改所必须要的空间,还会为SDS分配额外的未使用空间。其中,额外分配的未使用空间数量由以下公式决定:

  • 1.如果对SDS进行修改之后,SDS的长度(也即是len属性的长度)将小于1MB,那么程序分配和len属性同样大小的未使用空间,这时SDSlen属性的值将和free属性的值相同。
  • 2.如果对SDS进行修改之后,SDS的长度将大于等于1MB,那么程序会分配1MB的未使用空间。

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

例子
  • 例如,如果进行修改之后,SDS的len将变成13字节,那么程序也会分配13字节的未使用空间,SDS的buf数组的实际长度将变成13+13+1=27字节(额外的一字节用于保存空字符)

  • 例如,入宫进行修改之后,SDS的len将变成30MB,那么程序会分配1MB的未使用空间,SDS的buf数组的实际长度将为30MB+1MB+1byte

  • 例如,对于如图所示的SDS值s来说,如果执行sdscat(s, " Cluster"),
    那么sdscat将执行一次内存重分配操作,将SDS的长度修改为13字节,
    并将SDS的未使用空间同样修改为13字节
    在这里插入图片描述

  • 如果这时,我们再对s执行:sdscat(s, " Tutorial");那么这次sdscat将不需要执行内存重分配,因为未使用空间里面的13字节足以保存9字节的" Tutorial",执行sdscat之后的SDS,如下图所示
    在这里插入图片描述

  • 在扩展SDS空间之前,SDS API会先检查未使用空间是否足够,如果足够的话,API就会直接使用未使用空间,而无须执行内存重分配。
    通过这种预分配策略,SDS将连续增长N次字符串所需的内存重分配次数从必定N次降低为最多N次
    在这里插入图片描述

惰性空间释放

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

例子
  • 例如,sdstrim函数接受一个SDS和一个C字符串作为参数,从SDS左右两端分别移除所有在C字符串中出现过的字符.比如,对如图所示的SDS值s来说,执行
sdstrim(s,"XY")//移除SDS字符串中的所有"X"和"Y"

会将SDS修改成如下图
在这里插入图片描述

  • 注意执行sdstrim之后的SDS并没有释放多出来的8字节空间,而是将这些8字节空间作为未使用空间保留在了SDS里面,如果将来要对SDS进行增长操作的话,这些未使用空间就可能派上用场。
    在这里插入图片描述
  • 例如,现在对s执行sdscat(s," Redis");那么完成这次sdscat操作将不需要执行内存重分配:因为SDS里面预留的8字节空间已经足以拼接6个字节常的" Redis".通过惰性空间释放策略,SDS避免了缩短字符串所需的内存重分配操作,并为将来可能有的增长操作提供了优化,与此同时,SDS也提供了相应的API,让我们可以在有需要时真正地释放SDS的未使用空间,所以不用担心惰性空间释放策略会造成内存浪费
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/722710.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c++中多种类型sort()排序的用法(数组、结构体、pair、vector)

c中多种类型sort排序的用法 一、对数组排序1、默认排序2、自定义排序 二、对结构体进行排序三、对pair进行排序1、默认排序2、自定义排序 四、对vector进行排序1、默认排序2、去重排序3、自定义排序 一、对数组排序 1、默认排序 默认从小到大进行排序 #include <bits/std…

鸿蒙Harmony应用开发—ArkTS声明式开发(通用属性:拖拽控制)

设置组件是否可以响应拖拽事件。 说明&#xff1a; 从API Version 10开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 ArkUI框架对以下组件实现了默认的拖拽能力&#xff0c;支持对数据的拖出或拖入响应&#xff0c;开发者只需要将这些组件…

Xcode 15 适配 MonkeyDev

升级到Xcode15后,使用Xcode创建MonkeyApp后,运行会报错,本篇文章主要讲述此过程遇到的错误和解决办法。 问题1:找不到libc++.dylib文件 问题描述: Build input files cannot be found: /usr/lib/libstdc++.dylib, /usr/lib/libc++.dylib. Did you forget to declare th…

DHCP自动获取IP地址实验(华为)

思科设备参考&#xff1a;DHCP自动获取IP地址实验&#xff08;思科&#xff09; 一&#xff0c;实验目的 路由器搭载DHCP&#xff0c;让PC通过DHCP自动获取IP地址 二&#xff0c;不划分vlan--全局地址池 实验拓扑 配置命令 Router <Huawei>system-view [Huawei]ip po…

瑞_Redis_短信登录(一)

文章目录 项目介绍1 短信登录1.1 项目准备1.1.1 导入SQL1.1.2 导入后端项目1.1.3 导入前端项目 &#x1f64a; 前言&#xff1a;本文章为瑞_系列专栏之《Redis》的实战篇的短信登录章节的项目准备小节。由于博主是从B站黑马程序员的《Redis》学习其相关知识&#xff0c;所以本系…

LLM春招,搜广推,nlp(2)

Encoder和Decoder怎么交互的 decoder里面的K&#xff0c;V输入用的是encoder的输出&#xff0c;并不是共享了K, V参数&#xff0c;decoder每层的参数都是独立的。Decoder每层是有两块Attention的&#xff1a;一块是正常的Self-Attention&#xff0c;QKV的输入都来自Decoder上一…

docker单节点搭建在线商城

本文档使用到的软件包以上传到资源中 目录 1. 创建容器并配置基础内容 1.1 将gpmall-repo上传到容器中 1.2 添加yum源 2. 安装基础服务 2.1 安装JAVA环境 2.2 安装Redis缓存服务 2.3 安装Elasticsearch服务 2.4 安装Nginx服务 2.5 安装MariaDB数据库 2.6 安…

C++核心编程之内存分区模型,引用,函数提高

1&#xff0c;类型分区模型 c程序在执行中&#xff0c;将内存大方向划分为4个区域 1&#xff0c;代码区&#xff1a;存放函数体的二进制代码&#xff0c;由操作系统进行管理的 2&#xff0c;全局区&#xff1a;存放全局变量和静态变量以及常量 3&#xff0c;栈区&#xff1…

Spring框架的优点

Spring框架是一个开放源代码的J2EE应用程序框架&#xff0c;是针对bean的生命周期进行管理的轻量级容器。 Spring解决了开发者在J2EE开发中遇到的许多常见的问题&#xff0c;提供了功能强大的IOC、AOP及Web MVC等功能。 轻量级&#xff1a;相对于EJB框架而言。 Spring 体系结…

Windows安装SSH教程

Windows安装SSH教程 一、SSH1.SSH简介2.SSH功能3.SSH验证3.1 第一种级别&#xff08;基于口令的安全验证&#xff09;3.2 第二种级别&#xff08;基于密匙的安全验证&#xff09; 4.SSH层次4.1 传输层协议 [SSH-TRANS]4.2 用户认证协议 [SSH-USERAUTH]4.3 连接协议 [SSH-CONNEC…

node-day3-es6模块化+webpack

模块化 一、模块化分类 回顾node.js模块化&#xff1a; node.js遵循了CommonJS的模块化规范【见下文】&#xff0c;其中&#xff1a; 1.导入其它模块使用require()方法 2.模块对外共享成员使用module.exports对象 模块化的好处&#xff1a; 大家都遵守同样的模块化规范写代…

【学习笔记】开源计算机视觉库OPENCV学习方案

本文中&#xff0c;我们试图提供一些学习OpenCV的详细和实用资源&#xff0c;这些资源包括基础知识、进阶技巧以及实践项目&#xff0c;旨在帮助初学者和进阶学习者更好地掌握和使用OpenCV库。 一、学习资源 官方文档&#xff1a;OpenCV的官方文档是学习OpenCV的最佳起点。它包…

向量数据库Chroma教程

引言 随着大模型的崛起,数据的海洋愈发浩渺无垠。受限于token的数量,无数的开发者们如同勇敢的航海家,开始在茫茫数据之海中探寻新的路径。他们选择了将浩如烟海的知识、新闻、文献、语料等,通过嵌入算法(embedding)的神秘力量,转化为向量数据,存储在神秘的Chroma向量…

飞书云文档API操作详细介绍

1.场景分析 公司内部很多文档都是由多人进行维护的&#xff0c;随时发生变更&#xff0c;因此在利用这些数据的时候就需要直接读取云文档的数据&#xff0c;从而执行下一步动作。团队云文档api执行权限一般需要管理员审核才能使用。如果你就是管理员&#xff0c;那么恭喜你&am…

【DIY】钱包的“电子卫士”的制作

一、工作原理 钱包的“电子卫士”电路如图1所示&#xff0c;其核心元件是微型蜂鸣器专用音响集成电路A&#xff0c;它与压电陶瓷蜂鸣片B、电池G等组成了一个体积小巧、发声响亮的简易蜂鸣器。 平时&#xff0c;钱包通过尼龙线与插头XP相接&#xff0c;而XP插入插孔XS内&#x…

AndroidUI--setContentView

我们的Activity通常继承自Activity或者AppCompatActivity&#xff0c;这两个setContentView流程是不同的 一、继承自Activity 1、Activity.setContentView Activity中setContentVIew调用了getWindow().setContentView(view, params); getWindow返回的是mWindow&#xff0c;mWi…

RedisDesktopManager连接Ubuntu的Redis失败解决办法

配置redis 1.设置redis在后台服务&#xff0c;修改配置文件 默认情况下是 no ,修改为yes&#xff0c;可以后台服务 2、设置redis端口&#xff0c;默认端口是6379&#xff0c;可以根据自己的需要&#xff0c;找到/et/redis/redis.conf文件, 修改port 3、设置密码 配置文件中…

ubuntu20.04“E: 软件包 vim 没有可安装候选”“/etc/apt/sources.list:7 中被配置了多次”解决方法

问题一&#xff1a;ubuntu20.04安装vim时提示“E: 软件包 vim 没有可安装候选” **解决&#xff1a;**更换下载&#xff0c;比如我原先使用的是清华源&#xff0c;后切换成阿里云源&#xff0c;ubuntu直接在“软件和更新”切换 问题一解决。 问题二&#xff1a;ubuntu20.04提…

JavaEE+springboot教学仪器设备管理系统o9b00-springmvc

本文旨在设计一款基于Java技术的教学仪器设备销售网站&#xff0c;以提高网站性能、功能完善、用户体验等方面的优势&#xff0c;解决现有教学仪器设备销售网站的问题&#xff0c;并为广大教育工作者和学生提供便捷的教学仪器设备销售渠道。本文首先介绍了Java技术的相关基础知…

华为昇腾系列——入门学习

概述 昇腾&#xff08;Ascend&#xff09;是华为推出的人工智能处理器品牌&#xff0c;其系列产品包括昇腾910和昇腾310芯片等。 生态情况 众所周知&#xff0c;华为昇腾存在的意义就是替代英伟达的GPU。从事AI开发的小伙伴&#xff0c;应该明白这个替代&#xff0c;不仅仅是…