Redis简单动态字符串SDS

news2024/12/23 23:16:48

目录

前言

一.SDS定义

二.SDS与C字符串的区别

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

        2.2 杜绝缓冲区溢出

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

        2.3.1 空间预分配

        2.3.2 惰性空间释放

        2.4 二进制安全

        2.5 兼容部分C字符串函数

        2.6 总结

三.SDS缺点


前言

        Redis没有直接使用C语言形式的字符串来表示,而是自己构建了一种名为简单动态字符串的抽象类型SDS(simple dynamic string)。并将SDS作为默认字符串。

        在Redis中对于不会进行改变的字符串会使用C字符串,C字符串只会作为字符串字面量来使用。比如使用在打印日志。

        举个例子:

        在客户端输入命令 SET msg "hello world"

        在Redis中会新建一个键值对,其中,键值对的键和值都是字符串类型,底层实现都是用的是SDS。因为键和值都可以进行改变。

        处理字用来保存数据库的字符串值之外,SDS还可以被用作缓冲区,AOF模块中的AOF缓冲区,以及客户端状态的输入缓冲区。

一.SDS定义

        在redis源码中,在sds.h/sdshdrxx表示不同大小的sds:sdshdrxx会根据字符串的实际长度,选取合适的结构,最大化节省内存空间。

typedef char *sds;

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

__attribute__ ((__packed__)):结构体对象在大小实际不是变量字节的累加,会存在内存对齐的情况。而__attribute__ ((__packed__))是告诉编译器,不需要进行内存对齐。

变量: 

  •  len:记录buf数组中已使用字节数,等于SDS保存字符串长度,不包含'\0'。
  • alloc:记录buf数组总共分配的内存大小,不包含'\0'。
  • flags:记录当前字节数组的属性,使用的哪一个结构。前3位记录使用的SDS结构,后5位在sdshdr5中记录字符串长度。在其他结构中没有使用。
//flag定义
#define SDS_TYPE_5  0
#define SDS_TYPE_8  1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4

#define SDS_TYPE_MASK 7 //按位与,得到flags的低3位
#define SDS_TYPE_BITS 3 //用于右移动flags得到高5位。

//用来定义对应T结构SDS变量,s一般是buf的起始地址
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
//用来得到对应T结构SDS变量,s一般是buf的起始地址
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
//得到sdshdr5字符串长度
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
  • buf:保存真正的字符串的值,以及最后一个'\0'。

        SDS遵循C字符串以空字符结尾,保存空字符的一字节空间不计算在SDS的len里面,并且为空字符分配1字节的额外空间,以及添加空字符到字符串结尾等操作都是SDS自动完成的。

        在SDS以空字符串结尾的好处是,可以使用C字符串里面的函数。

二.SDS与C字符串的区别

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

        因为C字符串并不记录自身的长度信息,所以获取一个C字符串的长度需要遍历整个字符串,知对遇到的每一个字符进行计数,知道遇到空字符串为止。时间复杂度为O(N)。

        由于SDS在len字段记录了本身的长度,所以获取SDS字符串的长度复杂度为O(1)。

        2.2 杜绝缓冲区溢出

        C字符串使用C字符串函数进行拼接字符串时,当字符串的空间不够保存拼接的字符串,容易造成缓冲区溢出,还可能会修改其他字符串。

        所以在进行C字符串拼接时,需要程序员判断空间是否够保存拼接的字符串。不够需要开辟足够的空间。

        而SDS杜绝了缓冲区溢出的可能。在使用SDS API进行字符串拼接操作时。API会先检查SDS的剩余空间(alloc - len)是否满足修改的需求,通常会调用 sds.c/sdsMakeRoomFor 方法对 SDS 的剩余容量进行检查。不满足,API会自动将SDS的空间扩展到需要的大小,最后再进行修改操作。判断空间是否满足和修改SDS空间的大小都是SDS API自动完成的

static inline size_t sdsavail(const sds s) {
    unsigned char flags = s[-1];
    switch(flags&SDS_TYPE_MASK) {
        case SDS_TYPE_5: {
            return 0;
        }
        case SDS_TYPE_8: {
            SDS_HDR_VAR(8,s);
            return sh->alloc - sh->len;
        }
        case SDS_TYPE_16: {
            SDS_HDR_VAR(16,s);
            return sh->alloc - sh->len;
        }
        case SDS_TYPE_32: {
            SDS_HDR_VAR(32,s);
            return sh->alloc - sh->len;
        }
        case SDS_TYPE_64: {
            SDS_HDR_VAR(64,s);
            return sh->alloc - sh->len;
        }
    }
    return 0;
}

sds sdsMakeRoomFor(sds s, size_t addlen) {
    void *sh, *newsh;
    size_t avail = sdsavail(s);
    size_t len, newlen, reqlen;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen;
    size_t usable;

    /* Return ASAP if there is enough space left. */
    if (avail >= addlen) return s;

    len = sdslen(s);
    sh = (char*)s-sdsHdrSize(oldtype);
    reqlen = newlen = (len+addlen);
    assert(newlen > len);   /* Catch size_t overflow */
    if (newlen < SDS_MAX_PREALLOC)
        newlen *= 2;
    else
        newlen += SDS_MAX_PREALLOC;

    type = sdsReqType(newlen);

    /* Don't use type 5: the user is appending to the string and type 5 is
     * not able to remember empty space, so sdsMakeRoomFor() must be called
     * at every appending operation. */
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;

    hdrlen = sdsHdrSize(type);
    assert(hdrlen + newlen + 1 > reqlen);  /* Catch size_t overflow */
    if (oldtype==type) {
        newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);
        if (newsh == NULL) return NULL;
        s = (char*)newsh+hdrlen;
    } else {
        /* Since the header size changes, need to move the string forward,
         * and can't use realloc */
        newsh = s_malloc_usable(hdrlen+newlen+1, &usable);
        if (newsh == NULL) return NULL;
        memcpy((char*)newsh+hdrlen, s, len+1);
        s_free(sh);
        s = (char*)newsh+hdrlen;
        s[-1] = type;
        sdssetlen(s, len);
    }
    usable = usable-hdrlen-1;
    if (usable > sdsTypeMaxSize(type))
        usable = sdsTypeMaxSize(type);
    sdssetalloc(s, usable);
    return s;
}

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

        由于C字符串并不记录自身长度。在每次进行增长和缩短C字符串,程序总需要对这个C字符串进行一次内存重分配:

  • 如果程序执行的是增长操作,那么在执行前,需要先进行内存重分配来扩展底层数组空间,否则会造成缓冲区溢出。
  • 如果程序执行的是截断的操作,操作之后需要释放原来不使用的空间,否则会造成内存泄漏。

        因为内存分配涉及复杂的算法,并且需要执行系统调用,所以它通常是一个比较耗时的操作。但是Redis作为数据库,经常被用于速度要求严苛,数据会被频繁修改的场合。如果每次修改字符串都需要重新分配内存,会对Redis的性能照成影响。

        为了避免,Redis记录了字符串的长度和底层数组的长度。

        2.3.1 空间预分配

        空间预分配用于优化SDS的字符串增长操作。当SDS的API需要对底层数组进行空间扩展时,程序不仅会为SDS分配必须要的空间,还会分配额外未使用的空间。这样如果下一次增长字符串长度在剩余空间内,就不需要重新分配空间了。有效减少了空间分配次数。

        分配策略:

  • 当进行修改后,SDS的长度,也就是len的值小于1MB,那么程序会为SDS分配与拼接后的len值同样大小的未使用空间。即alloc-len = len。比如:进行拼接后SDS的len变成了13字节,那么程序也会分配13字节的未使用空间。SDS数组长度变成了13+13+1=27字节,最后一个字节保存空字符。
  • 当进行修改后,SDS的长度,也就是len的值大于等于1MB,那么程序会分配1MB的未使用空间。比如:进行拼接后SDS的len变成了30MB,实际SDS的长度变成了30MB+1MB+1byte大小。

        2.3.2 惰性空间释放

        惰性空间释放用于优化SDS的字符串缩短操作。当SDS缩短保存的字符串时,程序并不立即使用内存重新分配回收缩短多出来的字节,而是修改len值,剩余的空间可以为之后拼接字符串使用。

        通过惰性空间释放,即能避免缩短字符串的内存重新分配,还可以为将来的增长操作提供优化。

        于此同时SDS也提供了API,让我们在有需要的时候,释放SDS未使用空间。

        2.4 二进制安全

        C字符串的字符必须符合某种编码,并且由于C字符串是以空字符串结尾的,中间如果出现空字符,会被当做字符串结尾。导致后面的字符被省略。使得C字符串只能保存文本数据,而不能保存图像,音频,视频,压缩文件等二进制文件。

        而Redis的SDS的API是二进制安全的,所以API都会以处理二进制的方式来处理buf中的数据,程序不会对其中的数据做任何限制,过滤或者假设。数据在写入时什么样,读取时就什么样。

        由于SDS使用的时len来保存字符串长度,支持了二进制安全的实现。Redis不仅可以保存文本数据,还可以保存任何格式的二进制数据。

        2.5 兼容部分C字符串函数

        虽然SDS是二进制安全的,但是它仍然遵循C字符串以空字符串结尾的管理。即API会自动在分配空间时,为空字符串分配一个空间,并且会在SDS保存的数据末尾设置空字符串。

        这是为了让那些保存文本数据的SDS可以重用部分<string.h>库定义的函数。

        2.6 总结

C字符串SDS
获取字符串长度时间复杂度O(N)获取字符串长度时间复杂度O(1)
API不安全,可能造成缓冲区溢出API安全,不会造成缓冲区溢出
修改字符串长度N此必然需要进行N次内存重新分配修改字符串长度N此最多需要进行N次内存重新分配
只能保存文本数据可以保存文本和二进制数据
可以使用所有<string.h>库定义的函数可以使用部分<string.h>库定义的函数

三.SDS缺点

        从SDS结构可以看出,SDS除了保存字符串,还需要保存其他的数据入长度,空间大小,标记等。所以SDS的一个缺点是占内存。典型的一个用空间来换时间的结构。

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

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

相关文章

gRPC教程与应用

gRPC是是谷歌一个开源的跨语言的RPC框架&#xff0c;面向移动和 HTTP/2 设计。 grpc中文网 在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法&#xff0c;使得您能够更容易地创建分布式应用和服务。 gRPC 也是基于以下理念&#xff1…

python3+requests+unittest接口自动化测试

1.环境准备 python3 pycharm编辑器 2.框架目录展示 &#xff08;该套代码只是简单入门&#xff0c;有兴趣的可以不断后期完善&#xff09; &#xff08;1&#xff09;run.py主运行文件&#xff0c;运行之后可以生成相应的测试报告&#xff0c;并以邮件形式发送&#xff1b;…

【C++进阶】红黑树实现

文章目录 红黑树的概念红黑树的性质红黑树节点的定义红黑树结构红黑树的插入1.按照二叉搜索的树规则插入新节点2.进行旋转和变色源码 红黑树的验证中序遍历判断是否满足二叉搜索树判断是否满足红黑树 完整源码 红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但…

基于spss的多元统计分析 之 单/双因素方差分析 多元回归分析(1/8)

实验目的&#xff1a; 1&#xff0e;掌握单样本t检验、两样本t检验、配对样本t检验、单因素方差分析、多元回归分析的基本原理&#xff1b; 2&#xff0e;熟悉掌握SPSS软件或者R软件关于单因素、多因素方差分析、多元回归分析的基本操作&#xff1b; 3&#xff0e;利用实验指导…

2.3C++保护成员

C 保护成员 在C中&#xff0c;可以使用保护成员 protected&#xff0c;来提高代码的安全性。 我用大白话解释一下什么是保护成员&#xff1a;说白了就是为了防止其他类直接访问或修改其成员加的一个措施。 目的是保护&#xff0c;成员的私有性和可见性。 C 类的保护 可以为…

web 语音通话 jssip

先把封装好的地址安上&#xff08;非本人封装&#xff09;&#xff1a;webrtc-webphone: 基于JsSIP开发的webrtc软电话 jssip中文文档&#xff1a;jssip中文开发文档&#xff08;完整版&#xff09; - 简书 jssip使用文档&#xff1a;&#xff08;我没有运行过&#xff0c;但…

Nginx服务器,在window系统中的使用(前端,nginx的应用)

简介&#xff1a;Nginx是一个轻量级、高性能的HTTP和反向代理web服务器&#xff0c;且支持电子邮件&#xff08;IMAP/POP3&#xff09;代理服务&#xff0c;特点是占用内存少&#xff0c;并发能力强&#xff0c;给我们来了很多的便利&#xff0c;国内大部分网站都有使用nginx&a…

18款奔驰S350升级后排座椅记忆功能,提升您乘坐舒适性

带有记忆功能的座椅可以存储三个的座椅设置以及行车电脑中的舒适性设置。只要按一下按钮就可以跳到记忆模式&#xff0c;让座椅回到上一次设置。

使用 BigQuery Omni,发现跨云地理空间分析的优势

【本文由 Cloud Ace 整理发布。Cloud Ace 是谷歌云全球战略合作伙伴&#xff0c;拥有 300 多名工程师&#xff0c;也是谷歌最高级别合作伙伴&#xff0c;多次获得 Google Cloud 合作伙伴奖。作为谷歌托管服务商&#xff0c;我们提供谷歌云、谷歌地图、谷歌办公套件、谷歌云认证…

第十章详解synchronized锁升级

文章目录 升级的流程为什么要引入锁升级这套流程多线程访问情况具体流程 轻量级锁如何使用CAS实现轻量级锁CAS加锁成功CAS加锁失败CAS进行解锁 总结何时变为重量级锁 锁膨胀自旋优化 偏向锁主要作用偏向状态测试撤销偏向锁 撤销 - 调用对象 hashCode撤销 - 其它线程使用对象撤销…

js:codemirror实现在线代码编辑器代码高亮显示

CodeMirror is a versatile text editor implemented in JavaScript for the browser. It is specialized for editing code, and comes with a number of language modes and addons that implement more advanced editing functionality. 译文&#xff1a;CodeMirror是一个多…

第二章:软件工程师必备的网络基础

目录 一、网线的制作 二、集线器、交换机介绍 三、路由器的配置 一、网线的制作 1.1、水晶头 ​​​ 1.2、网线钳 1.3、网线的标准 T568A标准&#xff08;交叉线&#xff09;&#xff1a; 适用链接场合&#xff1a;电脑-电脑、交换机-交换机、集线器-集线器 接线顺序&…

【正点原子STM32连载】第三十九章 触摸屏实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

1&#xff09;实验平台&#xff1a;正点原子stm32f103战舰开发板V4 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id609294757420 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html# 第三…

有源电力滤波器及配电能效平台在污水处理厂中的应用

【摘要】为减少污水处理设备产生的各次谐波&#xff0c;通过确定主要谐波源&#xff0c;检测和计算谐波分量&#xff0c;采用有源电力滤波器进行谐波治理&#xff0c;大幅降低了电力系统中的三相电流畸变率&#xff0c;提高了电能质量&#xff1b;抑制了谐波分量&#xff0c;减…

doris docker部署和本地化部署 1.2.4.1版本

写在前面 以下操作语句按顺序执行即可&#xff0c;注意切换目录的命令一定记得执行&#xff0c;如果需要改动的地方会有${}注释&#xff0c;其余不需要任何改动&#xff0c;默认安装版本为1.12.4&#xff08;稳定版&#xff09; 本地化部署 下载 # 创建目录 mkdir /data/sof…

软件测试日常工作和前景是怎么样的?

笔者从测试的工作情况&#xff0c;职业发展&#xff0c;还有测试的工作日常等等来给大家讲解一下软件测试到底是什么样的工作&#xff1f; 通俗来说软件测试工程师就相当于一个质检员&#xff0c;专门处理软件测试质量的工作&#xff0c;不管是功能测试也好&#xff0c;性能测…

BK7231N开发平台原厂烧录工具使用说明

BK7231N开发平台原厂烧录工具使用说明 烧录流程介绍 1.打开原厂烧录工具 以管理员身份打开名为 bk_writer_gui_V1.6.3.exe 的可执行文件。 2. 烧录对象 烧录对象选择 BK7231n 3.烧录地址 当我们烧录UA文件的时候&#xff0c;需要把起始地址设置为&#xff1a; 0X00011000。…

Windows提示“找不到rgss202j.dll”怎么办?

Rgss202j.dll文件是Windows操作系统最重要的系统文件之一&#xff0c;它包含了一组程序和驱动函数。如果此文件丢失或损坏&#xff0c;驱动程序将无法正常工作&#xff0c;并且相应的应用程序也将无法正常启动且运行。通常情况下&#xff0c;造成Rgss202j.dll文件无法找到的原因…

爬虫 - ProtoBuf 协议

一、抓取请求 以下是请求的大致内容&#xff1a; 是乱码&#xff0c;需要解析。 二、解析 通过分析 request 和 response 的 Content-Type: application/x-protobuf 得知&#xff1a;使用了谷歌的 protobuf 协议来传输数据&#xff0c;需要破解。 大致破解过程&#xff…

随时随地保持连接:数字游民适用的远程桌面

随着世界迅速适应数字革命&#xff0c;一种全新的职业——数字游民应运而生。数字游民指利用技术远程办公的专业人群&#xff0c;这是一种允许人们在旅行中办公、不受地点限制的工作生活方式。游牧式工作生活趋势并非一时的风尚&#xff0c;而是我们工作观念的彻底转变&#xf…