Redis源码入门-字符串sds,sdshdr

CodeHorizonX
• 阅读 1507

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源码入门-字符串sds,sdshdr

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

// 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;
}
  1. 动态扩展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源码入门-字符串sds,sdshdr
点赞
收藏
评论区
推荐文章
把帆帆喂饱 把帆帆喂饱
2年前
Redis入门
Redis1、Redis概述Redis介绍Redis是一个开源的keyvalue存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sortedset–有序集合)和
最新Java大厂高频面试题,看这一篇就够了!
常见resdis面试真题40道(含解析)1.什么是Redis?2.Redis的数据类型?3.使用Redis有哪些好处?4.Redis相比Memcached有哪些优势?5.Memcache与Redis的区别都有哪些?6.Redis是单进程单线程的?7.一个字符串类型的值能存储最大容量是多少?8.Redis
Stella981 Stella981
3年前
Spring Boot Redis RedisTemplate 相关API介绍
Redis五大类型:字符串(String)、哈希/散列/字典(Hash)、列表(List)、集合(Set)、有序集合(sorted set)五种。SpringBoot集成redis的RedisTemplate,也分别提供的对这些数据类型的操作。主要有5大类:redisTemplate.opsForValue();//操作字符串redis
Stella981 Stella981
3年前
Linux实战教学笔记45:NoSQL数据库之redis持久化存储(一)
第1章redis存储系统1.1redis概述REmoteDIctionaryServer(Redis)是一个基于keyvalue键值对的持久化数据库存储系统。redis和大名鼎鼎的Memcached缓存服务软件很像,但是redis支持的数据存储类型比memcached更丰富,包括strings(字符串),lists(列
Stella981 Stella981
3年前
Redis源码入门
sds,全称SimpleDynamicStrings,是Redis自定义的一个字符串类型。typedefcharsds;看到这你肯定内心觉得Redis在逗你,这不就是一个字符数组么,怎么就SimpleDynamicStrings了呢!没错,我当时也是这么觉得的,但是仔细阅读源码后发现sds并不是一个人在战斗,它还有战
Stella981 Stella981
3年前
Redis从入门到放弃系列(一) String
Redis从入门到放弃系列(一)String本文例子基于:5.0.4字符串是Redis中最常见的数据结构,底层是采用SDS,是可以修改的字符串,类似ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。首先让我们来看一下该如何在redis里面使用字符串这种类型//将字符
Stella981 Stella981
3年前
Redis是什么
redis是Nosql数据库,是一个keyvalue存储系统。可用于缓存,事件发布或订阅,高速队列等场景。该数据库使用ANSIC语言编写,支持网络,提供字符串,哈希,列表,队列,集合结构直接存取,基于内存,可持久化。虽然redis是keyvalue的存储系统,但是redis支持的value存储类型是非常的多,比如字符串、链表、集合、有序集合和哈希。
Stella981 Stella981
3年前
Redis 中文入门
1)Redis简介Redis是一个开源的使用ANSIC语言编写、支持网络、可基于内存亦可持久化的日志型、KeyValue数据库。2)数据类型2.1.Redis的KeyRedis的key是字符串类型,但是key中不能包括边界字符,由于key不是binary
Stella981 Stella981
3年前
Redis实现之对象(一)
对象前面我们介绍了Redis的主要数据结构,如:简单动态字符串SDS、双端链表、字典、压缩列表、整数集合等。Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象,每种对象都用到了至少一种我们之前介绍的数据结构通过这五种
Stella981 Stella981
3年前
RedisTemplate操作命令
字符串操作redis储存的字符串都是以二进制的形式存在!字符串类型的内部编码有3种:int:8个字节的长整型。embstr:小于等于39个字节的字符串。raw:大于39个字节的字符串。Redis会根据当前值的类型和长度决定使用哪种内部编码实现。命令操作返回值set(K