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的优点如下:
- 常数复杂度获取字符串长度
 - 杜绝缓冲区溢出
 - 减少修改字符串长度所需的内存重分配次数
 - 二进制安全
 - 兼容部分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 当中,
                                    
                    
                
                
            
        
浙公网安备 33010602011771号