Redis源码入门

Stella981
• 阅读 653

sds,全称Simple Dynamic Strings,是Redis自定义的一个字符串类型。

typedef char *sds;

看到这你肯定内心觉得Redis在逗你,这不就是一个字符数组么,怎么就Simple Dynamic Strings了呢 !没错,我当时也是这么觉得的,但是仔细阅读源码后发现sds并不是一个人在战斗,它还有战友sdshdr,sdshdr是个五胞胎,分别是sdshdr5,sdshdr8,sdshd16,sdshdr32,sdshd64。块头从小到大。

sdshdr 全称 Simple Dynamic Strings Header

/* 因为生的跟别人不一样(内部结构不一样),老五(sdshdr5)从来不被使用 */
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 低三位表示类型, 高五位表示字符串长度 */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* 字符串长度*/
    uint8_t alloc; /* 分配长度 */
    unsigned char flags; /* 低三位表示类型,高五位未使用 */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* 字符串长度*/
    uint16_t alloc; /* 分配长度 */
    unsigned char flags; /* 低三位表示类型,高五位未使用 */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* 字符串长度*/
    uint32_t alloc; /* 分配长度 */
    unsigned char flags; /* 低三位表示类型,高五位未使用 */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* 字符串长度*/
    uint64_t alloc; /* 分配长度 */
    unsigned char flags; /* 低三位表示类型,高五位未使用 */
    char buf[];
};

知识点!这个很关键!!
__attribute__ ((__packed__))
待会你会看到如下代码:
(s)-(sizeof(struct sdshdr##T))) 、s[-1]、(char*)s-sdsHdrSize(s[-1])
这些指针之所以可以走位如此风骚,都归功于 __attribute__ ((__packed__))

这个命令的意思是 取消编译阶段的内存优化对齐功能.
ps: 关于内存补齐如果之前不知道,请自行百度。

所以,该结构在内存中的结构如下:

Redis源码入门

这样看,之前那些风骚的走位就很明了了。

// s减去sdshdr长度 = 指向sdshdr结构体的指针
(s)-(sizeof(struct sdshdr##T))) 、
// s前一个位置 = flags
s[-1]
// 与1相同效果
(char*)s-sdsHdrSize(s[-1])

有了上面的基础,看sds.c和sds.h里的代码就已经很容易了,我们重点看两个函数

  1. 创建sds字符串

    sds sdsnewlen(const void init, size_t initlen) { void sh; sds s; / 根据字符串的长度来决定sds的类型 / char type = sdsReqType(initlen); / 老五被歧视了 / if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8; / 计算sdsHeader的长度 / int hdrlen = sdsHdrSize(type); / 对应flags / unsigned char fp; / 开辟内存空间,+1是为了最后放一个\0,兼容传统C语言,入乡随俗 / sh = s_malloc(hdrlen+initlen+1); if (!init) memset(sh, 0, hdrlen+initlen+1); if (sh == NULL) return NULL; / 这走位,指向字符串开始的地方 / s = (char)sh+hdrlen; / 这走位,到flags了 / fp = ((unsigned char)s)-1; / 根据不同的类型,初始化sdsHeader */ 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; }

  2. 动态扩展sds空间

    sds sdsMakeRoomFor(sds s, size_t addlen) { void sh, newsh; / avail = alloc-len / size_t avail = sdsavail(s); size_t len, newlen; char type, oldtype = s[-1] & SDS_TYPE_MASK; int hdrlen; / 若剩下的空间足够,就不需要扩了 / if (avail >= addlen) return s; len = sdslen(s); sh = (char)s-sdsHdrSize(oldtype); newlen = (len+addlen); / Redis认为一旦被扩容了, * 那这个字符串被再次扩容的几率就很大,所以会在此基础上多加一些空间, * 防止频繁扩容 / if (newlen < SDS_MAX_PREALLOC) newlen = 2; else newlen += SDS_MAX_PREALLOC; / 重新计算type / type = sdsReqType(newlen); / 老五又被歧视了 / if (type == SDS_TYPE_5) type = SDS_TYPE_8; hdrlen = sdsHdrSize(type); if (oldtype==type) { / 当原类型与新类型一致,则在原有基础是realloc空间即可 / newsh = s_realloc(sh, hdrlen+newlen+1); if (newsh == NULL) return NULL; s = (char)newsh+hdrlen; } else { / 否则需要重新malloc一整块空间,然后拷贝 / 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; }

关注公众号:java宝典 Redis源码入门

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Karen110 Karen110
3年前
一篇文章带你了解JavaScript日期
日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用
Wesley13 Wesley13
3年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这