01、字符传实现为什么是SDS而不是char*?

news2024/11/17 11:25:56

问题:

1. sds 是什么 ?

2. sds 相对于char * 有什么好处 ?解决了哪些疑难杂症?

3. sds 有什么不足?可以优化的点?

思考下:

平常工作开发中,我们记录一条用户信息、订单信息,redis内部是怎么帮我们把数据存起来的呢,是随意allot 一个内存,放进去嘛 ?

键值对中的键是字符串,值有时也是字符串。我们在 Redis 中写入一条用户信息,记录了用户姓名、性别、所在城市等,这些都是字符串,如下所示:

SET user:id:100 {"name": "zhangsan", "gender": "M","city":"beijing"}

大家如果有实际使用过redis那么都应该知道,类似这种字符串的操作在Redis中其实是最常见的,那么既然它被使用这么频繁,字符串的存储需要满足什么要求吗 ?

  1. 能支持丰富且高效的字符串操作,比如字符串追加、拷贝、比较、获取长度等;
  2. 能保存任意的二进制数据,比如图片等 能尽可能地节省内存开销。

在C语言中,使用char来实现字符串存储,定义了很多方法来满足日常开发,比如字符串比较函数 strcmp、字符串长度计算函数 strlen、字符串追加函数 strcat 等。那么下面来看下为什么不能直接使用Char

为什么Redis 不使用C的char呢 ?

首先redis是一个纯内存操作,结合它的使用属性,可以想到redis 要求性能高、存储小等特点,那么反过来看下char是否可以满足呢?

首先以字符串‘redis’为例看下使用char的存储结构,一块连续的内存空间,依次存放了字符串中的每一个字符数组结构。

\0 ,表示一个字符串的结尾,在C语言中,char* 指针只是指向字符数组的起始位置,而字符数组的结尾位置就用”\0"表示

如strlen 函数流程:

通过一段代码,可以更清晰看到\0的作用,这里我创建了两个字符串变量 a 和 b,分别给它们赋值为"red\0is"和"redis\0”。然后,我用 strlen 函数计算这两个字符串长度,如下所示:

  include <stdio.h>
  include <string.h>
  int main()
  {
     char *a = "red\0is";
     char *b = "redis\0";
     printf("%lu\n", strlen(a));
     printf("%lu\n", strlen(b));
     return 0;
  }

代码执行后,输出结果分别是3和5,a =3 , b= 5 字节。那么看到这问题也就出来了,我们都知道Redis是有保存二进制数据的需求的。如果使用char来存储会使二进制数据被截断,完全支持不了。

另外,回过来看strlen函数,虽然可以计算字符串长度,但是它也会带来另一方面的负面影响,也就是会导致操作函数的复杂度增加。需要遍历字符数组中的每一个字符,才能得到字符串长度,所以这个操作函数的复杂度是 O(N)。

再来看另一个函数,也是常用的strcat追加功能。strcat 函数是将一个源字符串 src 追加到一个目标字符串的末尾。该函数的代码如下所示:

  char *strcat(char *dest, const char *src) {
     //将目标字符串复制给tmp变量
     char *tmp = dest;
     //用一个while循环遍历目标字符串,直到遇到"\0"跳出循环,指向目标字符串的末尾
     while(*dest)
        dest++;
     //将源字符串中的每个字符逐一赋值到目标字符串中,直到遇到结束字符
     while((*dest++ = *src++) != '\0' )
     return tmp;
  }

从代码中可以看到,strcat 函数和 strlen 函数类似,复杂度都很高,也都需要先通过遍历字符串才能得到目标字符串的末尾。对于 strcat 函数来说,还要再遍历源字符串才能完成追加。另外,它在把源字符串追加到目标字符串末尾时,还需要确认是否空间足够。

SDS的设计

SDS(即简单动态字符串)的数据结构。下面我们一起来看看SDS 结构设计

SDS 结构里包含了一个字符数组 buf[],用来保存实际数据。同时,SDS 结构里还包含了三个元数据,分别是字符数组现有长度 len 、分配给字符数组的空间长度 alloc ,以及 SDS 类型 flags

typedef char *sds;

Redis 源码中 SDS 的定义,Redis 使用 typedef 给 char* 类型定义了一个别名,这个别名就是 sds。如上。那么到这里也可看得出本质其实还是个字符数组,只是在字符数组基础上增加了额外的元数据。

接下来,看下sdsnewlen 函数:

sds sdsnewlen(const void *init, size_t initlen) {
    void *sh;  //指向SDS结构体的指针
    sds s;     //sds类型变量,即char*字符数组
    ...
    sh = s_malloc(hdrlen+initlen+1);   //新建SDS结构,并分配内存空间
    ...
    s = (char*)sh+hdrlen;              //sds类型变量指向SDS结构体中的buf数组,sh指向SDS结构体起始位置,hdrlen是SDS结构体中元数据的长度
    ...
    if (initlen && init)
        memcpy(s, init, initlen);    //将要传入的字符串拷贝给sds变量s
    s[initlen] = '\0';               //变量s末尾增加\0,表示字符串结束
    return s;

详细说下,创建一个SDS的过程:sdsnewlen 函数会新建 sds 类型变量(也就是 char* 类型变量),并新建 SDS 结构体,把 SDS 结构体中的数组 buf[] 赋给 sds 类型变量。最后,sdsnewlen 函数会把要创建的字符串拷贝给 sds 变量。

到此,我们已经了解SDS底层数据结构,那么对比一下与传统的C语言操作字符串,SDS有哪些优点:SDS 结构中记录了字符数组已占用的空间和被分配的空间,提高读写效率。同样以字符串追加为例,Redis 中实现字符串追加的函数是 sds.c 文件中的 sdscatlen 函数。

sds sdscatlen(sds s, const void *t, size_t len) {
    //获取目标字符串s的当前长度
    size_t curlen = sdslen(s);
    //根据要追加的长度len和目标字符串s的现有长度,判断是否要增加新的空间
    s = sdsMakeRoomFor(s,len);
    if (s == NULL) return NULL;
    //将源字符串t中len长度的数据拷贝到目标字符串结尾
    memcpy(s+curlen, t, len);
    //设置目标字符串的最新长度:拷贝前长度curlen加上拷贝长度
    sdssetlen(s, curlen+len);
    //拷贝后,在目标字符串结尾加上\0
    s[curlen+len] = '\0';
    return s;
}

同样,我们来画个流程图来梳理一下sdscatlen执行流程:

和char操作相比,SDS 通过记录字符数组的使用长度和分配空间大小,避免了对字符串的遍历操作,降低了操作开销,进一步就可以帮助诸多字符串操作更加高效地完成.

紧凑型字符串结构

        简单点说,sds设计了不同的结构头(也就是不同的类型),为了能灵活的保存不同大小的字符串,从而有效节省内存空间。保存不同大小的字符串时,结构头占用的内存空间也不一样,在保存小字符串时,结构头占用的空间也比较少。

        官方话语来说就是:SDS 一共设计了 5 种类型,分别是 sdshdr5、sdshdr8、sdshdr16、sdshdr32 和 sdshdr64。主要区别就在于,它们数据结构中的字符数组现有长度 len 和分配空间长度 alloc,这两个元数据的数据类型不同。

// attribute__ (__packed__) 采用紧凑的方式分配内存
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* 字符数组现有长度*/
    uint8_t alloc; /* 字符数组的已分配空间,不包括结构体和\0结束字符*/
    unsigned char flags; /* SDS类型*/
    char buf[]; /*字符数组*/
};

上面代码片段可以看到,len、 alloc 都是uint_t 类型。是一个8位无符号整数,每个占用一个字节的空间,也就是说当字符串类型是 sdshdr8 时,它能表示的字符数组长度2的8次方。

同理, sdshdr16、sdshdr32、sdshdr64 三种类型来说,它们的 len 和 alloc 数据类型分别是 uint16_t、uint32_t、uint64_t,即它们能表示的字符数组长度,分别不超过 2 的 16 次方、32 次方和 64 次方。

说到这里也就大概了解不同的类型的作用了,那么还有一点要提一下,字节对齐方式和紧凑的方式分配内存

对齐和紧凑分配内存

        在默认情况下,编译器会按照 8 字节对齐的方式,给变量分配内存。也就是说,即使一个变量的大小不到 8 个字节,编译器也会给它分配 8 个字节。

看下面的两个例子,大概就懂了

 include <stdio.h>
  int main() {
     struct s1 {
        char a;
        int b;
     } ts1;
     printf("%lu\n", sizeof(ts1));
     

    struct __attribute__((packed)) s2{
        char a;
        int b;
     } ts2;
     printf("%lu\n", sizeof(ts2));
  }

char 类型占用 1 个字节,int 类型占用 4 个字节.,sizeof(ts1) ==8 , sizeof(ts2) == 5,剩下的自己琢磨啦

小结

加油、学到就是赚到,哪怕应付面试呢

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

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

相关文章

SAP-QM-检验批和物料凭证

业务场景&#xff1a; 在做数字化项目中可能会导出一些数据&#xff0c;例如&#xff0c;通过检验批要找到物料凭证&#xff0c;因为启用了质检模块&#xff0c;收货操作是103105&#xff0c;当做103收货时产生检验批1000*************,然后通过QM系统的QA11决策之后收货&…

代码随想录Day25 回溯算法 LeetCode T51 N皇后问题

目录 前言 LeetCode T51 N皇后问题 题目思路: 回溯三部曲: 2.终止条件 3.一次搜索逻辑 4.isValid合法性判断 5.Array2List 题目代码: 总结: 前言 又来到了我们的周末,今天我们挑战一道困难题:N皇后问题,相信大家都玩过一个经典的小游戏:8皇后 游戏规则是:在一个n*n的…

Python学习第2天-安装pycharm

文章目录 前言一、下载二、安装1.选择安装目录2.安装配置 总结 前言 好用的工具可以极大地提高生产力&#xff0c;开发Python推荐使用jetbrains全家桶的pycharm。 一、下载 通过官网下载安装包。 二、安装 1.选择安装目录 2.安装配置 一路Next&#xff0c;安装完成 总结 …

ANR系列之八:疑难ANR问题处理记录

前言&#xff1a; 本文仅是记录作者自身处理过的ANR问题&#xff0c;以及帮助他人解决过的ANR问题。本文中所介绍的ANR处理记录仅供参考&#xff0c;并不适用所有场景。并且最终结论和分析并不一定就是绝对正确的。 案例1.页面切换时前台应用焦点未获得 案例编号&#xff1a;…

从零开始学CAPL

从零开始学CAPL CAPL和C语言的关系和介绍C语言的基础语法常量变量标识符关键字数据类型整数数据字符数据实型数据(浮点数据)运算符以及优先级类型转换printf函数 选择程序结构设计if语句switch语句 循环结构程序设计while语句do……while语句for语句break和continue 数组函数 C…

VueComponent的原型对象

一、prototype 每一个构造函数身上又有一个prototype指向其原型对象。 如果我们在控制台输入如下代码&#xff0c;就能看到Vue构造函数的信息&#xff0c;在他身上可以找到prototype属性&#xff0c;指向的是Vue原型对象&#xff1a; 二、__proto__ 通过构造函数创建的实例对…

Mybatis拦截器

MyBatis插件介绍 MyBatis提供了一种插件(plugin)的功能&#xff0c;虽然叫做插件&#xff0c;但其实这是拦截器功能。 MyBatis允许使用者在映射语句执行过程中的某一些指定的节点进行拦截调用&#xff0c;通过织入拦截器&#xff0c;在不同节点修改一些执行过程中的关键属性&…

apache搭建静态网站,moongoose搭建网站后台,出现的跨域问题解决

文章目录 1&#xff0c;问题描述1.1&#xff0c;当网页和后台是不同服务时会产生跨域问题1.2&#xff0c;跨域问题 2&#xff0c;nginx端口转发解决跨域问题2.1&#xff0c;下载并安装nginx2.1.1&#xff0c;解压后如下所示2.1.2&#xff0c;进入解压目录后&#xff0c;执行配置…

SAP-QM-动态检验规则

Dynamic Modification Rule &#xff08;动态修改规则&#xff09; 1、决定样本大小的方式有3种&#xff1a; 手动输入比例大小采样过程 物料主数据质量视图 2、采样过程的创建方式有2种 跟批量大小有关系&#xff1a;百分比/AQL跟批量大小没有关系&#xff1a;固定值 而当…

Jetpack:014-Jetpack中的小红点

文章目录 1. 概念介绍2. 使用方法2.1 Badge2.2 BadgedBox 3. 示例代码4. 内容总结 我们在上一章回中介绍了Jetpack库中底部栏相关的内容&#xff0c;本章回中主要介绍 小红点。闲话休提&#xff0c;让我们一起Talk Android Jetpack吧&#xff01; 1. 概念介绍 我们在本章回中…

Android C/C++ native编程NDK开发中logcat的使用

Android C/C native编程NDK开发中logcat的使用 前言具体用法 前言 在NDK开发过程中&#xff0c;C/C层&#xff0c;需要对代码进行一些调试&#xff0c;日志打印是我们解决异常或崩溃的重要手段&#xff0c;这里我就简单介绍下日志打印三步走。 首先我们先看下官方文档关于日志…

渗透测试--JWT攻防(一)

JWT简介 JWT代表JSON Web Token&#xff0c;它是一种用于安全地在不同实体之间传递信息的开放标准&#xff08;RFC 7519&#xff09;。JWT通常用于身份验证和授权领域&#xff0c;以及在网络应用程序和服务之间传递声明&#xff08;claims&#xff09;信息。 JWT的常见用途包括…

S7-1200通过CM CANopen模块与KINCO伺服连接

CM CANopen模块简介 CM CANopen模块&#xff08;Profinet转CANopen&#xff09;来自瑞典HMS &#xff0c;由西 门子授权HMS公司开发&#xff0c;与S7-1200完美兼容。 可做为S7-1200与CANopen/CAN设备之间的桥梁&#xff0c;能够联接任意 CANopen或CAN 2.0A设备到SIMATIC S7-1…

聊聊分布式架构10——Zookeeper入门详解

目录 01ZooKeeper的ZAB协议 ZAB协议概念 ZAB协议基本模式 消息广播 崩溃恢复 选举出新的Leader服务器 数据同步 02Zookeeper的核心 ZooKeeper 的核心特点 ZooKeeper 的核心组件 选举算法概述 服务器启动时的Leader选举 服务器运行期间的Leader选举 03ZooKeeper的…

PT100温度传感器

热电阻是中低温区&#xfe61;常用的一种温度检测器。它的主要特点是测量精度高&#xff0c;性能稳定。其中铂热电阻的测量精确度是&#xfe61;高的&#xff0c;它不仅广泛应用于工业测温&#xff0c;而且被制成标准的基准仪。金属热电阻的感温元件有石英套管十字骨架结构&…

智能洗地机哪个牌子好用?智能洗地机品牌排行榜

为了偷懒人类发明了扫把、拖把等手动清洁工具&#xff0c;随着技术的进步出现了吸尘器、扫地机器人等等智能产品&#xff0c;近几年洗地机又以快速、直接、高效对市场进行了“颠覆”&#xff0c;如何快速在洗地机市场中挑选到适合自己的智能洗地机呢&#xff0c;我们一起来看看…

2023-10-18 LeetCode每日一题(执行 K 次操作后的最大分数)

2023-10-18每日一题 一、题目编号 2530. 执行 K 次操作后的最大分数二、题目链接 点击跳转到题目位置 三、题目描述 给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。你的 起始分数 为 0 。 在一步 操作 中&#xff1a; 选出一个满足 0 < i < nums.length 的…

node快速搭建一个学习资料共享平台

概述 本文要实现的功能比较简单&#xff1a;1、将想要共享的文件分文件夹的组织起来&#xff1b;2、别人可以通过界面进行搜索&#xff1b;3、可以在线预览或下载文件。基于这样的需求&#xff0c;本文分享通过node如何实现这样的功能。 实现效果 实现 1. node端服务 node端…

QT 操作Windows系统服务

Windows服务是在Windows操作系统上运行的后台应用程序&#xff0c;它们在系统启动时自动启动&#xff0c;并在后台持续运行&#xff0c;不需要用户交互。Windows服务的作用包括但不限于以下几个方面&#xff1a;1. 提供系统功能&#xff1a;许多Windows服务提供了系统级的功能和…

【Java题】实现继承和多态的例子

一&#xff1a;题目 1.员工类Employee&#xff1a; &#xff08;1&#xff09;私有成员变量&#xff1a;姓名&#xff0c;年龄&#xff0c;工资 &#xff08;2&#xff09;提供无参&#xff0c;有参构造 &#xff08;3&#xff09;成员方法&#xff1a;work()方法——员工工作 …