我的编程空间,编程开发者的网络收藏夹
学习永远不晚

redis源码学习01:字符串sds

短信预约 信息系统项目管理师 报名、考试、查分时间动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

redis源码学习01:字符串sds

redis源码学习01:字符串sds

前言

本文是redis源码关于字符串处理的学习笔记,欢迎指正。 redis版本是5.0.5,redis的功能、用途及性能我就不做赘述了。

正文

进入正题,redis提供了自己的字符串存储及相关操作,源码文件在sds.h和sds.c里。 在学习代码的过程中发现redis使用了一个比较巧妙的设计,redis里存储字符串不是简单的使用C语言里的char *来存储,而是利用C语言指针可以加减运算的特性来封装字符串结构体。从而能够在常用的字符串处理函数里自动扩容;而且这个设计保证你在使用redis的字符串存函数同时也能使用全部的libc里的所有关于字符串的函数。 下面就来说说这个设计,首先看下一个宏定义:

typedef char *sds;

redis里给char *取了个别名sds,所以常用的跟字符串操作相关的函数也都是以sds开头如:

void sdssetlen(sds s, size_t newlen);
​size_t sdslen(const sds s);

接下来看下存储字符串的结构体:


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[];
};

可以看到定义了5个不同的结构体,我们知道redis数据都是缓存在内存中的,所以分别定义不同的结构体来存放不同长度的字符串,尽可能的减少内存的占用。 sdshdr5结构体基本不用,就先不介绍了,其它4个结构里都包含4个字段:

  • len:字符串长度,不同结构体支持最大长度不一样
  • alloc:分配的内存大小
  • flags:低3位存储类型,即结构类型
  • buf:用于存储字符串的内存起始地址,buf都是动态分配的

每个结构体通过__attribute__来告诉编译器不要进行字节对齐。不采用字节对齐一可以节省内存,另一个用途是可以通过指针的减法操作获取flags,能获取到flags后一切都好办了,flags里低3位是存放结构体类型的,在sds.h里定义如下:

#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     // 获取类型的掩码
#define SDS_TYPE_BITS 3     // 类型位数

当需要存储字符串时,先根据字符串长度选择一种结构体,然后申请一块空间,大小包括结构体大小及需要存储的字符串的大小,部分代码如下:

sds sdsnewlen(const void *init, size_t initlen) {
    void *sh;
    sds s;
    char type = sdsReqType(initlen);    // 根据字符串长度选择合适的结构体存储
    
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
    int hdrlen = sdsHdrSize(type);      // 计算结构体大小,用于申请内存空间使用
    unsigned char *fp; 

    sh = s_malloc(hdrlen+initlen+1);	// 申请的空间包含结束符
    if (init==SDS_NOINIT)
        init = NULL;
    else if (!init)
        memset(sh, 0, hdrlen+initlen+1);
    if (sh == NULL) return NULL;
    s = (char*)sh+hdrlen;       // s为存储字符串的起始位置
    /*
     * 其它代码
     * ......
     *
     * /

    return s;
}

redis会多申请一个字节用于存储结束符,而且自动加上这个结束符,这样libc里所有的字符串处理函数都能适用。函数里申请的空间包括结构体部分,但是返回值却是真正存放字符串的地址。那么redis是怎么将s和结构体关联起来的呢,先看sds.h里的两个宏:

// 根据字符串起始位置定义一个sdshdr**的结构体指针
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
// 将字符串起始位置的指针转化为sdshdr**的结构体指针
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))

因为申请的时候是连续的地址,所以这两个宏直接将地址减去结构体的长度就能获取到结构体的起始地址。 使用这两个宏需要知道结构体的类型,但是只有一个字符串的起始地址,那怎么知道是哪一种结构体存储的呢。 这时候结构体不采用内存对齐的方式就派上用场了,以获取字符串长度的函数为例:

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

因为结构体没有内存对齐,而且flags字段是char类型,所以直接s[-1]就能获取到flag,只要取到flag其它就都好办了。其它比如获取容量等操作都是通过这种方式处理的,这里就不再一一介绍了。

收尾

本文主要是介绍存储字符串结构的技巧,其它代码都相对简单。sds.h和sds.c里常用的储如字符串拼接、格式化等操作都是自动扩容的,扩容大小是指数增长的。有时候也有可能进行缩容,不管是扩容还是缩容都会导致原来的指针失效。所以使用的时候要小心指针失效的情况,大部分函数都得这么用:s = sdstrim(s,"Aa. :"); 就先到这吧,欢迎指正~

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

redis源码学习01:字符串sds

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

redis源码学习01:字符串sds

前言本文是redis源码关于字符串处理的学习笔记,欢迎指正。redis版本是5.0.5,redis的功能、用途及性能我就不做赘述了。正文进入正题,redis提供了自己的字符串存储及相关操作,源码文件在sds.h和sds.c里。在学习代码的过程中发现redis使
redis源码学习01:字符串sds
2019-03-04

Redis 学习笔记(一) 字符串 SDS

SDS 简单动态字符串。SDS的结构:struct sdshdr{int len;//记录BUF数组中已使用字节的数量 ,等于SDS所八寸字符串的长度int free;//记录BUF数组中未使用字节的数量char buf[];//字节数组,用于保存字符串}1、
Redis 学习笔记(一) 字符串 SDS
2016-03-29

redis源码阅读——动态字符串sds

redis中动态字符串sds相关的文件为:sds.h与sds.c一、数据结构redis中定义了自己的数据类型"sds",用于描述 char*,与一些数据结构 1 typedef char *sds; 2 3 /* Note: sdshdr5 is never
redis源码阅读——动态字符串sds
2015-11-21

redis 5.0.7 源码阅读——动态字符串sds

redis中动态字符串sds相关的文件为:sds.h与sds.c一、数据结构redis中定义了自己的数据类型"sds",用于描述 char*,与一些数据结构 1 typedef char *sds; 2 3 /* Note: sdshdr5 is never
redis 5.0.7 源码阅读——动态字符串sds
2020-03-12

Redis中的动态字符串学习教程

sds 的用途 Sds 在 Redis 中的主要作用有以下两个: 实现字符串对象(StringObject); 在 Redis 程序内部用作 char* 类型的替代品; 以下两个小节分别对这两种用途进行介绍。 实现字符串对象 Redis 是
2022-06-04

编程热搜

目录