Redis3.20阅读-SDS实现

声明:这是本人参考黄建宏的《redis设计与实现》(源码版本是redis3.0)来学习redis3.20源码的笔记,如果有什么不对的地方,欢迎大家指正,大家一起学习、一起进步,QQ:499656254。

一、SDS介绍

SDS又叫简单动态字符串,在Redis中默认使用SDS来表示字符串。比如在Redis中的键值对中的键一般都是使用SDS来实现。首先需要说明的是在Redis中,字符串不是用传统的字符串来实现,而是Redis自己构建了一个结构来表示字符串。优点如下:

1、O(1)时间内获取字符串长度。(依据其结构特性,只需要访问其结构体成员len既可获得字符串长度)

2、SDS提供的一些API操作,是二进制安全的(也就是不会因为空格等特殊字符而中断字符串)、不会溢出(API操作会检查其长度)

3、减少了修改字符串时带来的内存重分配次数。

对于增长字符串其采用的策略是检查修改之后的长度大小,如果小于1024*1024,则分配2倍的修改后的长度+1

对于减少的字符串其并不立即释放空间,而是回归到alloc中去。

这个构建的结构在Redis3.20中的表示如下(和Redis2.x中还是有一定区别的):

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 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    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[];
};

从代码中可以看出,SDS表示的字符串是有SDSheader和char*指针组成,而SDS的头部主要由四部分组成:

len:SDS字符串已使用的空间。

alloc:申请的空间,减去len就是未使用的空间,初始时和len一致。

flag:只使用了低三位表示类型,细化了SDS的分类,根据字符串的长度的不同选择不同的sds结构体,而结构体的主要区别是len和alloc的类型,这样做可以节省一                           部分空间大小,毕竟在redis字符串非常多,进一步的可以节省空间。

buf:  用了C的特性表示不定长字符串。

二、API学习

1、sdsnewlen函数

函数原型:sds sdsnewlen(const void *init, size_t initlen)

说明:sdsnewlen用来创建init所指向对象作为内容的SDS,比如mystring = sdsnewlen("abc",3)。其中sdsnew函数也是调用sdsnewlen函数来实现的

返回值:buf数组的指针位置

sds sdsnewlen(const void *init, size_t initlen) {
    void *sh;
    sds s;
    char type = sdsReqType(initlen);//根据initlen的长度,选择不同的type,进一步来节省内存空间
    /* 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;
    int hdrlen = sdsHdrSize(type);//返回sdshdr结构体大小
    unsigned char *fp; /* flags pointer. */

    sh = s_malloc(hdrlen+initlen+1);//底层调用malloc申请空间
    if (!init)
        memset(sh, 0, hdrlen+initlen+1);//若创建的sds对象为空,则空间赋值0
    if (sh == NULL) return NULL;//分配失败返回NULL
    s = (char*)sh+hdrlen;//指向buf数组
    fp = ((unsigned char*)s)-1;//指向flag
    switch(type) {//根据type不同对sdshdr结构体进行赋值,len和alloc设置为initlen
        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、sdsMakeRoomFor函数

函数原型:sds sdsMakeRoomFor(sds s, size_t addlen)

说明:实现扩充已有sds的可用空间为指定的大小,扩充规则是:当addlen的长度小于1024*1024时,则申请的空间是2*(addlen+len),否则扩充为1024*1024大小。

返回值:扩充后的sds对象

sds sdsMakeRoomFor(sds s, size_t addlen) {
    void *sh, *newsh;
    size_t avail = sdsavail(s);//返回剩余可用空间,即s->alloc - s->len
    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;
    else
        newlen += 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;

    hdrlen = sdsHdrSize(type);
    if (oldtype==type) {    //若类型和原有类型一样,则采用realloc分配空间,否则重新分配采用malloc函数分配。
        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;
}

  3、sdstrim函数

函数原型:sds sdstrim(sds s, const char *cset)

说明:从左右两边剔除sds对象包含集合CSET中的元素,内部通过memmove函数移位实现。

sds sdstrim(sds s, const char *cset) {
    char *start, *end, *sp, *ep;
    size_t len;

    sp = start = s;
    ep = end = s+sdslen(s)-1;
    while(sp <= end && strchr(cset, *sp)) sp++;
    while(ep > sp && strchr(cset, *ep)) ep--;
    len = (sp > ep) ? 0 : ((ep-sp)+1);
    if (s != sp) memmove(s, sp, len);
    s[len] = ‘\0‘;
    sdssetlen(s,len);
    return s;
}

三、结论

sds数据类型是redis里面常用的数据类型,所以其在设计优化上面有了一定的改动(相对于redis2.x版本),比如其数据结构发生了改变。最后想说下,其源码实现确实比较简单,但是代码写的很nice(至少目前的我还写不出来,不过我要加油)

时间: 2024-08-24 16:52:04

Redis3.20阅读-SDS实现的相关文章

Redis源码阅读-sds字符串源码阅读

redis使用sds代替char *字符串, 其定义如下: typedef char *sds; struct sdshdr { unsigned int len; unsigned int free; char buf[]; }; sds指向了char 字符串 sdshdr是字符串头 结构比较巧妙 使用char buf[]存放字符串实际内容 注意char *buf和char buf[]是不同的 sizeof(sdshdr)等于8,而不是我以为的12 连续内存结构如下: 0----7 sdshd

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

redis中动态字符串sds相关的文件为:sds.h与sds.c 一.数据结构 redis中定义了自己的数据类型"sds",用于描述 char*,与一些数据结构 1 typedef char *sds; 2 3 /* Note: sdshdr5 is never used, we just access the flags byte directly. 4 * However is here to document the layout of type 5 SDS strings. *

redis源码分析(2)-- 基本数据结构sds

一.sds格式 sds header定义: 1 struct sdshdr { 2 unsigned int len; 3 unsigned int free; 4 char buf[]; 5 }; sizeof(struct sdshdr)= 2*sizeof(unsigned int), char buf[]等价于char buf[0], 仅对编译器有效,并不实际占用存储. 其中len是使用的长度,free是剩余的长度,再加上一个C语言中的'\0'结束符 sizeof(buf) = len

《梦断代码》的阅读计划

作为一名计算机专业的学生,有一些此方面的书籍还是需要拜读的,一下就是我对<梦断代码>的阅读计划: 3.5~3.10   阅读第0章到第三章的内容: 3.11~3.15 阅读第四章到第六章的内容: 3.16~3.20 阅读第七章到第八章的内容: 3.21~3.25 阅读第九章到第十章的内容: 3.26~3.30 阅读剩下的内容: 阅读期间,有感而发之时会写一些随行笔记,预计三篇以上.

jQuery EasyUI API 中文文档

http://www.cnblogs.com/Philoo/tag/jQuery/ 共2页: 1 2 下一页 jQuery EasyUI API 中文文档 - 树表格(TreeGrid) 风流涕淌 2011-11-19 18:51 阅读:25025 评论:3 jQuery EasyUI API 中文文档 - 树(Tree) 风流涕淌 2011-11-18 20:13 阅读:31937 评论:2 jQuery EasyUI 1.2.4 API 中文文档(完整)目录 风流涕淌 2011-11-17

jfreechart 实例

http://blog.csdn.net/ami121/article/category/394379 jfreechart实例(三)股价K线波动图 package com.ami;/** *@ Email:[email protected] *@version:1.0 *@create_time:Apr 9, 20093:23:55 PM */import java.awt.Color;import java.awt.Dimension;import java.util.Date;import

【IPC通信】基于管道的popen和pclose函数

http://my.oschina.net/renhc/blog/35116 恋恋美食  恋恋美食 发布时间: 2011/11/12 23:20 阅读: 15897 收藏: 13 点赞: 5 评论: 0 标准I/O函数库提供了popen函数,它启动另外一个进程去执行一个shell命令行. 这里我们称调用popen的进程为父进程,由popen启动的进程称为子进程. popen函数还创建一个管道用于父子进程间通信.父进程要么从管道读信息,要么向管道写信息,至于是读还是写取决于父进程调用popen时传

返回一个整型数组中最大子数组的和(02)

组员:刘伟 http://www.cnblogs.com/Lw-1573/p/5323542.html 1.要求: 输入一个整形数组,数组里有正数也有负数.数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和.如果数组A[0]……A[j-1]首尾相邻,允许A[i-1],…… A[n-1],A[0]……A[j-1]之和最大.同时返回最大子数组的位置.求所有子数组的和的最大值. 2分析: 这个题目是在上个题目的基础的加上数组首位相连,并该数组最大子数组的和,实现方法就是把环变成直线的方法,

xiaoluo同志Linux学习之CentOS6.4

小罗同志写的不错,弄个列表过来啊 Linux学习之CentOS(三十六)--FTP服务原理及vsfptd的安装.配置 xiaoluo501395377 2013-06-09 01:04 阅读:5665 评论:8 Linux学习之CentOS(三十五)--配置域从DNS服务器以及缓存DNS服务器 xiaoluo501395377 2013-06-07 00:09 阅读:6348 评论:1 Linux学习之CentOS(三十四)--配置域主DNS服务器 xiaoluo501395377 2013-0