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

news2024/9/23 23:26:23

Redis 是内存数据库,高效使用内存对 Redis 的实现来说非常重要。

看一下,Redis 中针对字符串结构针对内存使用效率做的设计优化。

一、SDS的结构

c语言没有string类型,本质是char[]数组;而且c语言数组创建时必须初始化大小,指定类型后就不能改变,并且字符数组的最后一个元素总是空字符 '\0' 。

以下展示了一个值为 "Redis" 的 C 字符串:

Redis没有直接使用C语言的字符串方式,而是构建了一种简单动态字符串(Simple dynamic string, SDS)的类型,Redis中的字符串底层都是使用SDS结构进行存储,比如包含字符串的键值对底层都是使用SDS结构实现的。

SDS结构定义在sds.h中

1

2

3

4

5

6

7

8

9

10

11

12

13

struct sdshdr{

    int len;//SDS保存的字符串长度

    int free;//buf数组中未使用字节数量

    char buf[];//字符数组,保存字符串

}

  

最后一个字节保存了空字符'\0',保留了C字符串的规范,使得SDS结构的字符串,可以重用一部分C函数库的函数。

二、为什么不使用C字符串

主要是因为C字符串有以下缺点:

  • 获取字符串长度时间复杂度为O(N):C字符串获取长度需遍历整个字符串,遇到'\0'空字符为止。
  • 缓冲区溢出:比如在进行字符串追加操作时,如果没有分配足够的内存,就会造成内存溢出。
  • 内存重分配:每次增长或者截短字符串,程序都要对保存C字符串的数组进行内存重分配操作,而内存重分配涉及复杂的算法,并可能需要执行系统调用,所以它通常比较耗时。
  • 空字符问题:C字符串中间不能保存空格,否则程序遍历是会误认为是字符串的末尾。这一限制导致C字符串只能存储文本数据,不能保存像图片、音视频、压缩文件等二进制数据。

三、怎样解决C字符串问题

 

1、SDS通过len属性记录了SDS长度,所以获取长度的时间复杂度为O(1),即strlen命令的时间复杂度是O(1)。

2、SDS空间分配策略避免了缓冲区溢出:当对SDS进行修改时,会先检查SDS空间是否满足修改,不满足会自动扩展到所需大小,然后才执行修改。

3、较少修改字符串时内存重分配次数:SDS中的free记录buf字节数组中未使用的字节。

redis通过free属性实现空间预分配、惰性空间释放两种优化策略。

  • 空间预分配:当对SDS进行增长操作时,程序不仅会分配修改所必须得空间,还会为SDS分配额外的未使用空间。通过预分配策略,减少了连续执行字符串增长操作时内存重分配次数。
  • 惰性空间释放:当对SDS进行截短操作时,程序并不会立即回收缩短后多出来的字节所占用的内存,而是使用free属性记录多出来的字节数,以供将来使用。如果将来要对这个SDS进行增长操作,未使用空间可能就派上用场,并且增长操作也不一定会执行内存重分配。

SDS结构中的buf字节数组,是二进制安全的,不仅可以保存字符,也可以保存二进制数据。

SDS保留了C字符串的惯例,将数据的末尾设置为空字符'\0',SDS中之所以保留这一规范是可以重用C字符串函数库的一部分函数,例如追加字符串。

四、对字符串的进一步优化

Redis string的三种编码:

  • int 存储8个字节的长整型(long,2^63-1 )
  • embstr, embstr格式的SDS (Simple Dynamic String)
  • raw, raw格式的SDS,存储大于44个字节的长字符串

int类型就是指的是数字,那么raw、embstr都代表的是字符串有什么异同吗,下面我们分析下。

图中展示了两者的区别,可以看到embstr将redisObject和SDS保存在连续的64字节空间内,这样可以只需要一次内存分配,而对于raw来说,SDS和redisObject分离,需要两次内存分配,而且占用更多的内存空间。

可以看到embstr在3.2+中使用了叫sdshdr8的结构,在该结构下,元数据只需要3个字节,而Redis需要8个字节,所以总共64个字节,减去redisObject(16字节),再减去SDS的原信息,最后的实际内容就变成了44字节和39字节。

当字符串小于等于 44 字节时,Redis 就使用了嵌入式字符串的创建方法,以此减少内存分配和内存碎片。

下面这张图展示了 createEmbeddedStringObject 创建嵌入式字符串的过程:

总之,只要记住,Redis 会通过设计实现一块连续的内存空间,把 redisObject 结构体和 SDS 结构体紧凑地放置在一起。

这样一来,对于不超过 44 字节的字符串来说,就可以避免内存碎片和两次内存分配的开销了。

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

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

相关文章

图片转PDF怎么转换?快学习这三种免费转换方法!

图像转PDF功能是指将图像文件转换为PDF文件的过程。PDF(PortableDocumentFormat)它是一种文件类型,可以存储许多元素,如文本、图像和报告。PDF文档具有跨平台、可打印、可搜索等优点,因此广泛应用于文档共享、文档存储…

Qt扫盲-QAbstractSeries理论总结

QAbstractSeries理论总结 一、概述二、常用函数1. 属性2. 设置功能3. 显示隐藏4. 与 绘图的交互 三、信号 一、概述 QAbstractSeries类是所有Qt图表线的基类。通常,特定于序列类型的继承类会被使用,而不是这个基类。这个基类只是提供了一些管理和控制这…

多功能科学计算器:Magic Number 2 Mac中文

Magic Number Mac - 让数学更简单。当你能正确地看待数学,能够输入你的想法,并凭直觉做每件事时,数学就会变得轻而易举。从日常数学到高级科学,Magic Number 让您事半功倍——无论您的水平如何。欢迎需要的朋友下载使用&#xff0…

IDEA中使用Git提交代码

在IDEA中使用git提交代码到远程仓库,整体可分为如下几个步骤: 前提:注册有GitHub或者gitee账号;本地安装有git。 1.创建远程仓库(github或者gitee); 2.创建本地仓库并提交代码到本地仓库&#x…

2023年如何成为一名优秀的大前端Leader?

目录 一、0-1开发vs低代码 二、优点与缺点 先以JNPF为例,展开说说优点: 1、开发周期短(这点我愿称之为神): 2、开发成本低 3、助力企业适用市场 再来说说缺点: 1、平台越成熟,费用越高 …

【动态规划】经典问题第三组---背包问题基础

前言 小亭子正在努力的学习编程,接下来将开启算法的学习~~ 分享的文章都是学习的笔记和感悟,如有不妥之处希望大佬们批评指正~~ 同时如果本文对你有帮助的话,烦请收藏点赞关注支持一波, 感激不尽~~ 刷题专栏在这里~~ 简单介绍一下什么是背包问…

再学C语言50:C库中的字符串函数(2)

一、strcmp()函数 功能&#xff1a;对字符串内容进行比较&#xff0c;如果两个字符串参数相同&#xff0c;函数返回0 示例代码&#xff1a; /* test of strcmp() function */ #include <stdio.h> #include <string.h>#define NAME "Forster"int main(…

rem实现移动端自适应

rem实现自适应的原理&#xff1a;就是屏幕的宽度/任意数&#xff08;推荐设计稿除下来是整数&#xff0c;方便计算&#xff09;&#xff0c;接着设置根html的font-size为这个数&#xff0c;比如设计师给我们的设计稿宽度为750px&#xff0c;我们可以用750/7.5得到100再赋值给ht…

rnn、lstm、cnn、transformer

rnn不能并行的原因&#xff1a;不同时间步的隐藏层之间有关联。 rnn中batch的含义 如何理解RNN中的Batch_size&#xff1f;_batch rnn_Forizon的博客-CSDN博客 rnn解决的问题 不定长输入带有顺序的序列输入1 rnn前向传播 2 rnn中的反向传播 还有loss对其他参数的求导&#…

Flutter渲染原理

一 Widget Element RenderObject 之间的关系 1 Widget 在Flutter 中&#xff0c;万物皆是Widget,无论是可见的还是功能型的。一切都是Widget. 官方文档中说的Widget 使用配置和状态来描述View 界面应该长什么样子。 它不仅可以表示UI元素&#xff0c;也可以表示一些功能性的…

前端学习:HTML JavaScript

目录 一、JavaScript 使HTML页面更具有动态性和交互性 浏览器中的 JavaScript 能做什么&#xff1f; 二、 HTML三、HTML标签 ​编辑四、JavaScript 的功能示例 1. JavaScript 能够更改内容&#xff1a; 2. JavaScript能够更改样式&#xff1a;3.JavaScript能够更改属性 五、…

拼多多运营中需要采集淘宝天猫京东平台商品详情页面数据上架拼多多店铺,如何使用技术封装接口实现

业务背景&#xff1a;电商平台趋势&#xff0c;平台化。大家可以看到大的电商都开始有自己的平台&#xff0c;其实这个道理很清楚&#xff0c;就是因为这是充分利用自己的流量、自己的商品和服务大效益化的一个过程&#xff0c;因为有平台&#xff0c;可以利用全社会的资源弥补…

FT2000+ openEuler 20.03 virsh创建qemu kvm虚拟机 启动qemu kvm

安装qemu、libvirt yum install libvirt libvirt-client -y yum install qemu -y 安装固件包 yum install edk2-aarch64 固件文件 配置/etc/libvirt/libvirtd.conf auth_tcp "sasl" listen_tcp 1 listen_tls 0 tcp_port "16509" unix_sock_dir …

RK3588_X703 音频调试笔记

x703项目扩接板有接喇叭音频&#xff0c;硬件如下&#xff1a; 喇叭SPK播放无声的时候&#xff0c;首先要测R43贴片电压正常。 需要dts中正确配置SPK_CTL_H的GPIO脚&#xff1a; es8316_sound: es8316-sound {status "okay";compatible "rockchip,multicodec…

javaScript---js如何实现继承

目录 1、构造函数继承 2、原型链继承 3、组合继承 4、class继承 5、寄生组合继承 JavaScript 是以对象为基础&#xff0c;以函数为模型&#xff0c;以原型为继承的面向对象开发模式。 javascript继承的作用&#xff1a; 可以不调用“父类”的构造方法就创造新的实例&…

JavaScript 基础入门速成上篇

JavaScript 嵌入页面的方式 1. 行间事件 <button onclick"alert(点击按钮)">按钮</button> 2. script标签 <script type"text/javascript">console.log(Hello javascript !) </script> 3. 外部引入 <script type"t…

并发编程三要素:可见性、原子性、有序性

一、介绍 1、什么是可见性、原子性、有序性&#xff1f; 可见性&#xff08;visibility&#xff09;&#xff1a;指一个线程对共享变量的修改能够被其他线程立即看到的特性。在多线程环境下&#xff0c;如果一个线程修改了一个共享变量的值&#xff0c;那么其他线程可能无法立…

时隔两个多月,一起来看ChatGPT现况如何?

ChatGPT这股风吹了两个多月&#xff0c;时至今日&#xff0c;各平台上与ChatGPT相关的文章&#xff0c;到现在依旧拥有着不小的流量。三月中旬上线了ChatGPT-4&#xff0c;与我们的文心一言前后脚发布&#xff0c;而后阿里的“通义千问”也展现了不俗的实力&#xff0c;那到现在…

【多线程】初识多线程

1. 为什么要学习多线程&#xff1f;首先相信各位小伙伴在学习 JavaSE 的时候&#xff0c;肯定写过一些小游戏吧&#xff0c;比如猜数字&#xff0c;关机小程序...但是如果现在要在猜数字小游戏上面加上一个功能&#xff0c;设定20秒没猜中&#xff0c;就判定游戏失败&#xff0…

数据结构:什么是堆,和二叉树有什么关系

堆栈模型 JS 代码执行时&#xff0c;值类型变量存储在栈&#xff0c;引用类型变量存储在堆。 // 变量 a 存储在栈里 let num1 1 let num2 num1 num2 2 // 这时打印 num1 是 1&#xff0c;num2 是 2。// { a: 1 } 存在堆里&#xff0c;obj1 只是一个指针引用 let obj1 { a…