美文网首页
reids string

reids string

作者: Wu杰语 | 来源:发表于2018-09-25 22:53 被阅读0次

redis中支持的数据结构都是经过设计优化的数据结构和算法。

1. redis string数据结构

见sds.h

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

#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

string存类型或者字符串,如果是字符串,使用sds中这样的设计。

类型的区别是长度的不同,根据不同的长度用不同的类型

  • sdshdr8 表示可以存储2^8 -1长度的字符串
  • sdshdr16 表示可以存储2^16 -1长度的字符串
  • sdshdr32 表示可以存储2^32 -1长度的字符串
  • sdshdr64 表示可以存储2^64 -1长度的字符串

2. string操作

sds sdsnewlen(const void *init, size_t initlen);
sds sdsnew(const char *init);
sds sdsempty(void);
sds sdsdup(const sds s);
void sdsfree(sds s);
sds sdsgrowzero(sds s, size_t len);
sds sdscatlen(sds s, const void *t, size_t len);
sds sdscat(sds s, const char *t);
sds sdscatsds(sds s, const sds t);
sds sdscpylen(sds s, const char *t, size_t len);
sds sdscpy(sds s, const char *t);

void sdsclear(sds s);
int sdscmp(const sds s1, const sds s2);
sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count);
void sdsfreesplitres(sds *tokens, int count);
void sdstolower(sds s);
void sdstoupper(sds s);
sds sdsfromlonglong(long long value);
sds sdscatrepr(sds s, const char *p, size_t len);
sds *sdssplitargs(const char *line, int *argc);
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
sds sdsjoin(char **argv, int argc, char *sep);
sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen);

/* Low level functions exposed to the user API */
sds sdsMakeRoomFor(sds s, size_t addlen);
void sdsIncrLen(sds s, ssize_t incr);
sds sdsRemoveFreeSpace(sds s);
size_t sdsAllocSize(sds s);
void *sdsAllocPtr(sds s);

/* Export the allocator used by SDS to the program using SDS.
 * Sometimes the program SDS is linked to, may use a different set of
 * allocators, but may want to allocate or free things that SDS will
 * respectively free or allocate. */
void *sds_malloc(size_t size);
void *sds_realloc(void *ptr, size_t size);
void sds_free(void *ptr);

3. redis string为何这么设计

redis为何要设计一个这样的数据结构,这是最大的问题。C字符串不能满足要求码?如果要这么设计,肯定是在算法上有优化。

《redis设计与实现》总讲解了:

  • 常数复杂度获取字符串长度。
  • 杜绝缓冲区溢出。
  • 减少修改字符串长度时所需的内存重分配次数。
  • 二进制安全。
  • 兼容部分 C 字符串函数。

3.0 typdef char* sds

typedef的最大作用是用来表达语义,在这里

3.1 O(1)复杂度的读取

这个很容易看出,在数据结构中的第一项,就是长度

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

3.2 杜绝缓冲器溢出

sds sdscat(sds s, const char *t) {
    return sdscatlen(s, t, strlen(t));
}

sds sdscatlen(sds s, const void *t, size_t len) {
    size_t curlen = sdslen(s);

    s = sdsMakeRoomFor(s,len);
    if (s == NULL) return NULL;
    memcpy(s+curlen, t, len);
    sdssetlen(s, curlen+len);
    s[curlen+len] = '\0';
    return s;
}

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;
    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) {
        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;
}

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

static inline void sdssetlen(sds s, size_t newlen) {
    unsigned char flags = s[-1];
    switch(flags&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            {
                unsigned char *fp = ((unsigned char*)s)-1;
                *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
            }
            break;
        case SDS_TYPE_8:
            SDS_HDR(8,s)->len = newlen;
            break;
        case SDS_TYPE_16:
            SDS_HDR(16,s)->len = newlen;
            break;
        case SDS_TYPE_32:
            SDS_HDR(32,s)->len = newlen;
            break;
        case SDS_TYPE_64:
            SDS_HDR(64,s)->len = newlen;
            break;
    }
}

防溢出可以从sdscat看到,在拼接之前,会使用sdsMakeRoomFor (s, len)重新计算s,逻辑上,当可分配空间足够的时候,直接返回s;当不足够,需要扩展,但是没有改变类型的时候,就需要realloc空间,当改变了类型的时候,需要重新分配内存。

3.3 减少修改字符串长度时所需的内存重分配次数

  • 预分配,见上面代码sdsMakeRoomFor,这里有内存分配
  • 惰性释放,主要体现在数据结构中的alloc和len,一开始alloc等于len,如果有释放,len会减少,alloc不会减少,当向sds中追加的时候,如果alloc-len足够大,就直接追加到该内存中了,不需要再另外分配内存了。

3.4 二进制安全

C字符串,只能存放字符串,不能存放中间有‘\0’的字符,而redis string的这种设计,可以保存,这就是二进制安全。

4 小结

redis的string数据结构设计,来实现redis string算法,在处理小字符串时的确没有什么优势,当存储超大数据的时候,它的优势就非常明显了,这种设计需要仔细体悟。

相关文章

  • reids string

    redis中支持的数据结构都是经过设计优化的数据结构和算法。 1. redis string数据结构 见sds.h...

  • Reids常用基础命令

    Reids常用命令,按照数据类型和用途分类: 1、string类 设置key:set key string_val...

  • redis使用场景

    reids各个数据类型存储最大容量 Strings类型:一个String类型的value最大可以存储512M Li...

  • centOS-7 安装 redis4.0.10

    获取reids (可直接去官网下载该包,上传至服务器) 安装reids需要依赖 源码安装reids 修改配置文件 ...

  • reids

    http://www.cnblogs.com/cly84920/p/4426422.html

  • Reids

    安装https://www.jianshu.com/p/6b5eca8d908b

  • RedisUtil

    reids的工具类整理

  • Reids 字典

    Reids 字典dict 1、字典结构 包含两个哈希表dictht ht[2],用于rehash,rehash包含...

  • reids基础

    redis的基本操作 TYPE获得键值的数据类型 TYPE命令用来获得键值的数据类型,返回值可能是string...

  • 查看进程ps -ef | grep *

    例如 ps -ef | grep javaps -ef | grep reids

网友评论

      本文标题:reids string

      本文链接:https://www.haomeiwen.com/subject/uevkoftx.html