Redis 源码 分析 SDS(一)

SDS
redis用于存储 字符串的实现类型,它替代了C里面的字符串类型。

struct sdsdr{
//已用字节长度
int len;
//剩余未使用空间
int free;
//字节数组
char buf[];	
}

当你执行 SET key rvalue 存储键值对时用的就是sds类型。

其实也 很容易理解 就像Java里实现的String 一样 他有length 和 capacity
如果是使用 原生C的字符串 首先你要获取到这个字符串的长度还要循环遍历 这对有大量读写的redis来说太不经济了 而且每次修改键值对等类型 都要重新分配内存 这样 的话 频繁的内存操作 会非常影响性能的 所以 事先预留一部分空间 假设使用了 10个字节 那按 80% 预留 8个字节 在redis中小于1M =1024char[]的 字节 再同等的分配一样的空闲空间 大于等于1M的数据则分配1M的空闲空间 同样的 要把某个字符串变短 也只是 在原空间上改变空闲空间大小 和长度 , 并不是真正的去申请一块内存。
在这里插入图片描述
那么为下次写入留有一定的余地,不需要一定就要重新分配内存再去复制。 另外一点是 存储的char 数据最后以 “\0” 结尾 就像c语言里默认字符串一样 这个结尾符是不算在 空间或者使用空间中的。 就像上面那张图 实际上是11个 但是 并不会去记录 '\0’这个字节

存储格式

redis 存储char 都是经过二进制转换后的 方式存储的 这样就避免了在C 字符串那样存储’\0’会导致扫描结尾出现异常

在这里插入图片描述
所以redis可以存储 任何类型的文件 因为它是以二进制来存储的 它读取的时候 是0101010 避免了某些不安全的因素。
在这里插入图片描述
所以SDS的优点如下:

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

如果 熟悉java 的话 其实相当于实现了一个简单的StringBuilder类 的功能

sds sdsMakeRoomFor(sds s, size_t addlen) {

    struct sdshdr *sh, *newsh;

    // 获取 s 目前的空余空间长度
    size_t free = sdsavail(s);

    size_t len, newlen;

    // s 目前的空余空间已经足够,无须再进行扩展,直接返回
    if (free >= addlen) return s;

    // 获取 s 目前已占用空间的长度
    len = sdslen(s);
    sh = (void*) (s-(sizeof(struct sdshdr)));

    // s 最少需要的长度
    newlen = (len+addlen);

    // 根据新长度,为 s 分配新空间所需的大小
    if (newlen < SDS_MAX_PREALLOC)
        // 如果新长度小于 SDS_MAX_PREALLOC 
        // 那么为它分配两倍于所需长度的空间
        newlen *= 2;
    else
        // 否则,分配长度为目前长度加上 SDS_MAX_PREALLOC
        newlen += SDS_MAX_PREALLOC;
    // T = O(N)
    newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);

    // 内存不足,分配失败,返回
    if (newsh == NULL) return NULL;

    // 更新 sds 的空余长度
    newsh->free = newlen - len;

    // 返回 sds
    return newsh->buf;
}

SDS 字符结构 实现方法

sds sdsnewlen(const void *init, size_t initlen)  //初始化字符串内存空间 如果传入值为空指针 不初始化分配的内存 initlen 为初始字符串空间长度 如果传入是字符串指针部位空 则初始化空间内存为0 返回为 长度为 传入字符串指针长度 空闲空间为0 的字符串 字符串结尾为'\0'
sds sdsempty(void) //初始化 空的 初始长度为0的字符串 
	return sdsnewlen("",0);
sds sdsnew(const char *init) {
    size_t initlen = (init == NULL) ? 0 : strlen(init);
    return sdsnewlen(init, initlen);
}
//初始化创建一个 长度为 传入字符串的 sds 长度为  buf的长度为传入字符串的长度 未使用空间为0
sds sdsdup(const sds s) {
    return sdsnewlen(s, sdslen(s));
}
//相当于 根据给定的sds再去 创建copy一份一样的
void sdsfree(sds s) {
    if (s == NULL) return;
    zfree(s-sizeof(struct sdshdr));
}
//释放sds的内存空间
void sdsupdatelen(sds s)
//  如果字符串中有'\0' 那么会设置成第一个'\0'的索引
void sdsclear(sds s)  //在不变内存空间的情况下 改变保存的字符串
sds sdsMakeRoomFor(sds s, size_t addlen) 
//对空间进行扩展 如果当前sds 有足够的空间直接返回传入的sds 反之重新申请内存空间 容量为 原来dsd长度 加上  addlen *2 内存空间不足返回NULL
sds sdsRemoveFreeSpace(sds s) //重新分配内存空间 是dsd 大小只是刚好保存了字符串 没有空闲空间
size_t sdsAllocSize(sds s) //返回dsd分配的内存字节数
void sdsIncrLen(sds s, int incr) //追加使用空间的大小 从空余空间哪里扣减
sds sdsgrowzero(sds s, size_t len) //将 sds 扩充至指定长度,未使用的空间以 0 字节填充。
sds sdscatlen(sds s, const void *t, size_t len) //将长度为 len 的字符串 t 追加到 sds 的字符串末尾
sds sdscat(sds s, const char *t) //将给定字符串 t 追加到 sds 的末尾
sds sdscatsds(sds s, const sds t) //相当于cat追加字符串
//将字符串 t 的前 len 个字符复制到 sds s 当中,
sds sdscpylen(sds s, const char *t, size_t len)
sds sdscpy(sds s, const char *t)// 将字符串复制到 sds 当中,
posted @ 2019-12-15 14:23  caomaoboy  阅读(151)  评论(0)    收藏  举报