文章目录

    • sds 结构分析
    • 基本操作
      • 创建字符串
      • 释放字符串
      • sdsMakeRoomFor 扩容
    • 小tip:`__attribute__ ((__packed__))`

阅读源码之前,先接几个问题,我觉得还蛮有意思的。

Q1:如何实现一个扩容方便且二进制安全(不会被\0打断)的字符串呢?
Q2:SDS如何兼容C语言函数呢?
Q3:SDS为了节约内存都秀了什么操作呢?
Q4:SDS是如何扩容的?
Q5:SDS是如何减少拷贝次数的?


题外话:这种模式我还挺喜欢的,也写过一些源码分析类的博客,但是感觉看完之后就没了,收效甚微。看nginx的时候,除了惊叹于其鬼斧神工的架构设计,以及比较火的那几点问题之后,也没学到多少编程技法(我主要编程技法都是在STL源码里学的,当然萃取是没看明白),不过如果采用这种启发式学习的方式可能会好一些,也能让人更愿意看源码吧。


sds 结构分析

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 {//该字段记录的字符串的长度,可以在常数时间获取//由于有长度记录变量len,在读写字符串的时候不依赖于‘\0’,从而保证了二进制安全性。uint8_t len; /* used */  //可存储的最大字符串长度为 2^8=256uint8_t alloc; /* excluding the header and null terminator *///低三位表示字符串的类型,高五位只在sd5中使用,不过看着架势sd5是被打入冷宫了呀。unsigned char flags; /* 3 lsb of type, 5 unused bits *///柔性数组,保存一个空字符作为buf的结尾,不计入len、alloc,以此兼容 C 语言的 strcmp、strcpy 等函数。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[];
};

A String value can be at max 512 Megabytes in length,如果拿那个64来算,会只有这点吗?
但是一个字符串过大也没用。

使用柔性数组除了省内存,还有一个好处,柔型数组的内存和结构体是连续的,可以很方便的通过柔型数组的首地址偏移得到结构体的首地址。

接下来看一下是如何的节约内存的:

这是sdshdr5的,里面的 unsigned8 对应一个字节。后面的自行脑补。


基本操作

创建字符串

sds sdsnewlen(const void *init, size_t initlen) {void *sh;sds s;char type = sdsReqType(initlen);/* Empty strings are usually created in order to append. Use type 8* since type 5 is not good at this. */if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;    //SDS_TYPE_5 强制转换为 SDS_TYPE_8int hdrlen = sdsHdrSize(type);    //计算不同头部的所需的那个 unsigned 长度unsigned char *fp; /* flags pointer. */sh = s_malloc(hdrlen+initlen+1);    //留了一个给‘\0’if (sh == NULL) return NULL;if (init==SDS_NOINIT)init = NULL;else if (!init)memset(sh, 0, hdrlen+initlen+1);s = (char*)sh+hdrlen;       //直接指向buffp = ((unsigned char*)s)-1;   //buf首地址偏移switch(type) {case SDS_TYPE_5: {  //这步略显多余了哈,有的代码还在,但是已经死了*fp = type | (initlen << SDS_TYPE_BITS);break;}case SDS_TYPE_8: {SDS_HDR_VAR(8,s);sh->len = initlen;sh->alloc = initlen;*fp = type;break;}case SDS_TYPE_16: {SDS_HDR_VAR(16,s);sh->len = initlen;sh->alloc = initlen;*fp = type;break;}case SDS_TYPE_32: {SDS_HDR_VAR(32,s);sh->len = initlen;sh->alloc = initlen;*fp = type;break;}case SDS_TYPE_64: {SDS_HDR_VAR(64,s);sh->len = initlen;sh->alloc = initlen;*fp = type;break;}}if (initlen && init)memcpy(s, init, initlen);s[initlen] = '\0';return s;
}

至于为什么要把sd5打入冷宫?可能是因为太短了吧,承受变长风险能力不够、


释放字符串

/* Free an sds string. No operation is performed if 's' is NULL. */
void sdsfree(sds s) {if (s == NULL) return;s_free((char*)s-sdsHdrSize(s[-1]));    //这里是直接释放内存了
}

不过这些优秀项目怎么能没有内存池呢,明着暗着都会有的。

/* Modify an sds string in-place to make it empty (zero length).* However all the existing buffer is not discarded but set as free space* so that next append operations will not require allocations up to the* number of bytes previously available. */
void sdsclear(sds s) {sdssetlen(s, 0);s[0] = '\0';
}

该有的都会有的。


sdsMakeRoomFor 扩容

扩容流程图:

/* Enlarge the free space at the end of the sds string so that the caller* is sure that after calling this function can overwrite up to addlen* bytes after the end of the string, plus one more byte for nul term.** Note: this does not change the *length* of the sds string as returned* by sdslen(), but only the free buffer space we have. */
sds sdsMakeRoomFor(sds s, size_t addlen) {void *sh, *newsh;size_t avail = sdsavail(s);size_t len, newlen;char type, oldtype = s[-1] & SDS_TYPE_MASK;int hdrlen;/* Return ASAP if there is enough space left. */if (avail >= addlen) return s;len = sdslen(s);sh = (char*)s-sdsHdrSize(oldtype);newlen = (len+addlen);if (newlen < SDS_MAX_PREALLOC)newlen *= 2;elsenewlen += 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;//此役过后,指向buf的指针被更新了hdrlen = sdsHdrSize(type);if (oldtype==type) {newsh = s_realloc(sh, hdrlen+newlen+1);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(hdrlen+newlen+1);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);}sdssetalloc(s, newlen);return s;
}

注意,分支上半部分是 realloc,下半部分是 malloc,注意区分二者区别。


小tip:__attribute__ ((__packed__))

在结构体声明当中,加上attribute ((packed))关键字,它可以做到让我们的结构体,按照紧凑排列的方式,占用内存。
简单的说,就是取消内存对齐。

这个 tip 哪里来的呢?翻到开头再看看。这个编程技法需要特别关注,稍不留神就错过了。

一般情况下,结构体会做内存对齐,以sd32为例,对齐前按4字节对齐,大小为12字节。取消对齐后,大小为9字节(buf不要面子的)。
而且,对齐后,可以直接通过 buf 的首地址向前偏移一位找到 flags ,如果不这样,各位可以自己思考一下要如何找到 flags,那就几乎成了一个 “鸡/蛋” 的死结了(不知道类型,怎么着偏移量?不知道偏移量,怎么找类型?)。


那各位自己解答开头的问题吧,溜了溜了。

【redis源码学习】simple dynamic strings(简单动态字符串 sds)相关推荐

  1. Redis源码初探(1)简单动态字符串SDS

    前言 现在面试可太卷了,Redis基本是必问的知识点,为了在秋招中卷过其他人(虽然我未必参加秋招),本菜鸡决定从源码层面再次学习Redis,不过鉴于本菜鸡水平有限,且没有c语言基础,本文不会对源码过于 ...

  2. Redis源码剖析(十)简单动态字符串sds

    在对象系统概述中发现,好像所有和字符串有关的内容都有sds的存在,实际上,它是Redis内部对于c字符串的封装,所谓c字符串,其实就是char *,在sds.h头文件中可以清楚的看到它的定义 //sd ...

  3. Redis5.0源码解析(一)----------简单动态字符串(SDS)

    基于Redis5.0 Redis 没有直接使用 C 语言传统的字符串表示(以空字符结尾的字符数组,以下简称 C 字符串), 而是自己构建了一种名为简单动态字符串(simple dynamic stri ...

  4. Redis内部数据结构详解之简单动态字符串(sds)

    本文所引用的源码全部来自Redis2.8.2版本. Redis中简单动态字符串sds数据结构与API相关文件是:sds.h, sds.c. 转载请注明,本文出自:http://blog.csdn.ne ...

  5. redis源码学习-03_动态字符串SDS

    概述 简单动态字符串(SDS, Simple Dynamic String)是 Redis 底层所使用的的字符串表示(而不是使用传统的 C 字符串). redis需要的不仅仅是一个字符串变量,而是一个 ...

  6. Redis源码学习(20),学习感悟

      最近学习Redis源码也有半个月的时间了,有不少收获也有不少感悟,今天来好好聊聊我学习的感悟. 1 发现问题   人非圣贤孰能无过,只要是人难免会犯错,回顾我之前的学习历程,其实是可以发现不少的问 ...

  7. Redis源码阅读笔记(1)——简单动态字符串sds实现原理

    首先,sds即simple dynamic string,redis实现这个的时候使用了一个技巧,并且C99将其收录为标准,即柔性数组成员(flexible array member),参考资料见这里 ...

  8. 【Redis学习笔记】2018-05-30 Redis源码学习之Ziplist、Server

    作者:施洪宝 顺风车运营研发团队 一. 压缩列表 压缩列表是Redis的关键数据结构之一.目前已经有大量的相关资料,下面几个链接都已经对Ziplist进行了详细的介绍. http://origin.r ...

  9. redis源码学习笔记目录

    Redis源码分析(零)学习路径笔记 Redis源码分析(一)redis.c //redis-server.c Redis源码分析(二)redis-cli.c Redis源码剖析(三)--基础数据结构 ...

  10. 【redis源码学习】redisObject

    使用的是redis6.0.6版本,因为我第一次接触 redis 时它就是这个最新稳定版. 文章目录 robj 数据类型 编码类型 随机应变的对象编码 回到robj robj redis中的数据对象 s ...

最新文章

  1. 沈阳建立通用航空产业基地,开辟国内首家无人机专用空域
  2. java文件放桌面怎么运行不了,不可能使用Java桌面在网络上启动文件吗?
  3. 从零开始入门 K8s | K8s 安全之访问控制
  4. 服务器采购框架合同协议书范本,手写一个满足WSGI协议的Server
  5. http之SessionCookie
  6. 项目推进计划表_项目为啥延期之一:计划问题
  7. Java web 实现视频在线播放的常用几种方法
  8. 荣耀6plus安装linux程序,华为 荣耀6Plus驱动程序下载和安装
  9. IT服务管理的实施过程
  10. 主板24pin接口详图_老电源也兼容 简单DIY 24Pin转接线
  11. 阿里巴巴推进中国中产阶级奢侈消费
  12. npm创建Vue工程【element UI】
  13. 去中心化和p2p网络以及中心化为核心的传统通信
  14. 对不起,我轻视了google的公关能力
  15. Shader的基础知识
  16. Nvidia Agx Xavier平台MIPI CSI-DHPY驱动调试
  17. 显示器的 VGA、HDMI、DVI 和DisplayPort接口有什么区别?
  18. L2-021 点赞狂魔 c++结构体排序
  19. 进程fork和exec ---Unix网络编程笔记
  20. Windows沙拉:为什么下载的文件打开时会有警告,而且会被“锁定”?

热门文章

  1. IT界郎朗上口的名言
  2. php md5加密 java代码_Java实现MD5加密及解密的代码实例分享
  3. canvas绘制矩形
  4. Android基础课程:原生视频播放器(播放网络资源)
  5. 接收大专、中专毕业生规定
  6. linux中find命令列举,Linux find命令常见用法汇总
  7. Qualcomm DragonBoard 410c Display之DSI浅析
  8. MKVToolNix for Mac(mkv视频编辑工具)
  9. 为什么极品飞车服务器维修,极品飞车9主机怎么总是显示失去和局域网服务器的联系...
  10. canvas入门,一篇博文带你学会用代码绘画,直击实战案例!