redis 字符串
概述
redis 没有使用 c 语言风格的字符串表示(以 "\0" 作为结尾), 而是使用自定义的 sds 结构
字符串结构
定义位置 (src/sds.h)
// 类型别名, 用于指向 sdshdr 的 buf 属性 typedef char *sds; // 字符串对象的结构 struct sdshdr { // 字符串长度 int len; // 字符串剩余可用空间的长度 (不包括 "\0") int free; /* * 数据空间 * sds 指向的地址 * 遵循以 "\0" 作为结尾, 这样对于 sds 变量可以使用 c 标准库的函数 * 比如: printf("%s", sdshdr->buf) */ char buf[]; };
len 字段
字符串长度 (不包括 "\0")
通过 len 长度来判断字符串的结尾, 二进制安全
常数复杂度获取字符串长度
若不设置 len 长度, 会存在缓冲区溢出问题, 如: strcat 函数, 此函数假定已经为字符串连接留下了空间, 一旦假定不成立, 就会缓冲区溢出
sds 结构完全杜绝了缓冲区溢出的问题, 在进行字符串操作的时候, 会判断空间是否满足要求 举例: // 字符串连接 sds sdscat(sds s, const char *t) { return sdscatlen(s, t, strlen(t)); } // 将长度为 len 的字符串 t 追加到 s 后面 sds sdscatlen(sds s, const void *t, size_t len) { struct sdshdr *sh; // 原有字符串长度 size_t curlen = sdslen(s); /* * 扩展 sds 空间 * 根据 s 的 free 字段是否满足 len 长度, 决定是否扩展 */ s = sdsMakeRoomFor(s,len); // 扩展失败, 直接返回 if (s == NULL) return NULL; // 复制 t 中的内容到字符串后部 sh = (void*) (s-(sizeof(struct sdshdr))); memcpy(s+curlen, t, len); // 更新属性 sh->len = curlen+len; sh->free = sh->free-len; // 添加新结尾符号 s[curlen+len] = '\0'; // 返回新 sds return s; }
- sdsMakeRoomFor(s, len): 对 s 进行操作时候判断剩余空间是否满足, 决定是否扩展
- sh = (void *) (s - (sizeof(struct sdshdr))): 对于字符串的操作, 根据 sds 地址计算出 sdshdr 的地址, 然后进行相应的属性赋值等操作
free 字段
减少内存预分配带来的系统调用所造成的性能损耗
c 语言对 string 结构并未进行任何的封装, 单纯的以 "\0" 作为结束符, 所以对字符串进行操作时, 比如增长操作时, 需要重新分配内存, 否则会造成缓冲区溢出; 缩减操作时, 需要重新分配内存, 否则会造成内存泄漏
空间预分配 (字符串增长操作)
/* * 为 sds 分配空间 */ 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; newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1); // 内存不足,分配失败,返回 if (newsh == NULL) return NULL; // 更新 sds 的空余长度 newsh->free = newlen - len; // 返回 sds return newsh->buf; }
- 分配内存的策略: 若新的字符串长度小于 SDS_MAX_PREALLOC (1024 * 1024), 则分配2倍的空间, 若大于, 则 += SDS_MAX_PREALLOC
惰性空间释放 (字符串缩短操作)
字符串在进行缩短操作时, 不会立即通过内存分配回收空闲空间, 而是将其更新到 free 字段, 以待将来增长操作时使用
sds api (src/sds.c)
函数 | 作用 | 备注 |
---|---|---|
sdsnewlen | 创建一个 sds, 使用指定的字符串和长度 | sds sdsnewlen(const void *init, size_t initlen) |
sdsempty | 创建一个空 sds | sds sdsempty(void) |
sdsnew | 创建一个 sds, 使用指定的字符串 | sds sdsnew(const char *init) |
sdsdup | 复制给定的 sds | sds sdsdup(const sds s) |
sdsfree | 释放给定的 sds | void sdsfree(sds s) |
sdsupdatelen | 更新 sds 的长度字段 (free, len) | void sdsupdatelen(sds s) |
sdsclear | 重置 sds 字符串对象为空 | void sdsclear(sds s) |
sdsMakeRoomFor | sds 字符串增长 addlen长度 (判断空余空间) | sds sdsMakeRoomFor(sds s, size_t addlen) |
sdsRemoveFreeSpace | 移除 sds 空闲空间 (free字段) | sds sdsRemoveFreeSpace(sds s) |
sdsAllocSize | 返回给定 sds 对象存储所分配的空间 | size_t sdsAllocSize(sds s) |
sdsIncrLen | sds 增长 incr 长度 (更新 len, free) | void sdsIncrLen(sds s, int incr) |
sdsgrowzero | 将 sds 增长到 len 长度, 超出部分用 0 填充 | sds sdsgrowzero(sds s, size_t len) |
sdscatlen | 将字符串 t 的 len 长度追加到 s 后面 | sds sdscatlen(sds s, const void *t, size_t len) |
sdscat | 将字符串 t 追加到 s 后面 | sds sdscat(sds s, const char *t) |
sdscatsds | 将 sds 结构的 t 追加到 s 后面 | sds sdscatsds(sds s, const sds t) |
sdscpylen | 将 t 的 len 长度的字符串复制给 s, 作为新 s | sds sdscpylen(sds s, const char *t, size_t len) |
sdscpy | 将字符串 t 复制给 s, 覆盖 s 原有字符串 | sds sdscpy(sds s, const char *t) |
sdsll2str | 将 long long 类型转换为 string 类型 | int sdsll2str(char *s, long long value) |
sdsull2str | 将 unsign long long 类型转换为 string 类型 | int sdsull2str(char *s, unsigned long long v) |
sdsfromlonglong | 用 long long 类型创建一个 sds 类型字符串 | sds sdsfromlonglong(long long value) |
sdscatvprintf | 将格式化后的字符串追加到 s 后 | sds sdscatvprintf(sds s, const char *fmt, va_list ap) |
sdscatprintf | 将格式化后的字符串追加到 s 后 | sds sdscatprintf(sds s, const char *fmt, ...) |
sdscatfmt | 将格式化后的字符串追加到 s 后 (redis自定义的格式标识符) | sds sdscatfmt(sds s, char const *fmt, ...) |
sdstrim | 将 s 两端去掉指定字符集合中的字符 | sds sdstrim(sds s, const char *cset) |
sdsrange | 截取 s 中的一段, 并赋值给 s | void sdsrange(sds s, int start, int end) |
sdstolower | 将 s 转换为小写字母 | void sdstolower(sds s) |
sdstoupper | 将 s 转换为大写字母 | void sdstoupper(sds s) |
sdscmp | 比较 s1, s2 的大小 | int sdscmp(const sds s1, const sds s2) |
sdssplitlen | 以指定分隔符 sep 将 s 分割成多个 token, 返回 sds 数组 | sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) |
sdsfreesplitres | 释放 sds 数组的 count 个 sds 内存空间 | void sdsfreesplitres(sds *tokens, int count) |
sdscatrepr | 将 p 字符串的 len 长度中的转义字符跳脱转义显示出来, 追加到 s 后面 | sds sdscatrepr(sds s, const char *p, size_t len) |
hex_digit_to_int | 将16进制字符转换为10进制的数值 | int hex_digit_to_int(char c) |
sdssplitargs | 将给定字符串转义字符进行转义返回, 并按照 space 作为分隔符进行分隔, 分隔后的参数列表保存在 argc | sds *sdssplitargs(const char *line, int *argc) |
sdsmapchars | 将 from 中的字符与 to 中的 setlen 字符一一对应, 对 s 进行替换 | sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) |
sdsjoin | 将 sds 数组中的 argc 个 sds 按照 sep 分隔符连接 | sds sdsjoin(char **argv, int argc, char *sep) |