redis源码分析(二)、sds动态字符串学习总结
2017-12-20 16:33
1011 查看
sds字符串
Redis 只会使用 C 字符串作为字面量, 在大多数情况下, Redis 使用 SDS (Simple Dynamic String,简单动态字符串)作为字符串表示。 比起 C 字符串, SDS 具有以下优点:常数复杂度获取字符串长度。
杜绝缓冲区溢出。
减少修改字符串长度时所需的内存重分配次数。
二进制安全。
兼容部分 C 字符串函数。
根据传统, C 语言使用长度为N+1的字符数组来表示长度为 N 的字符串,并且字符数组的最后一个元素总是空字符 '\0' 。
C 语言使用的这种简单的字符串表示方式, 并不能满足 Redis 对字符串在安全性、效率、以及功能方面的要求, 本节接下来的内容将详细对比 C 字符串和 SDS 之间的区别, 并说明 SDS 比 C 字符串更适用于 Redis 的原因。
SDS又叫简单动态字符串,在Redis中默认使用SDS来表示字符串。比如在Redis中的键值对中的键一般都是使用SDS来实现。首先需要说明的是在Redis中,字符串不是用传统的字符串来实现,而是Redis自己构建了一个结构来表示字符串。优点如下:
1、O(1)时间内获取字符串长度。常数复杂度获取字符串长度(依据其结构特性,只需要访问其结构体成员len既可获得字符串长度)
2、杜绝缓冲区溢出,另外SDS还提供的一些API操作,是二进制安全的(也就是不会因为空格等特殊字符而中断字符串)、不会溢出(API操作会检查其长度)
3、减少了修改字符串时带来的内存重分配次数。
对于增长字符串其采用的策略是检查修改之后的长度大小,如果小于1024*1024,则分配2倍的修改后的长度+1
对于减少的字符串其并不立即释放空间,而是回归到alloc中去。(自动查阅redis内存分配与释放策略)
typedef char *sds;//底层用于存储字符串的数据结构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 { //低三位保存type,高5位保存长度 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表示的字符串是有SDS header和char*指针组成,而SDS的头部主要由四部分组成:
len:SDS字符串已使用的空间。
alloc:申请的空间,减去len就是未使用的空间,初始时和len一致。
flag:只使用了低三位表示类型,细化了SDS的分类,根据字符串的长度的不同选择不同的sds结构体,而结构体的主要区别是len和alloc的类型,这样做可以节省一部分空间大小,毕竟在redis字符串非常多,进一步的可以节省空间。
buf: 用了C的特性表示不定长字符串。
除了sdshdr5之外,其他结构都是相似的。我们先看看sdshdr,它只有flags和buf成员,其中flag空间被C充分利用,其第三位保存了SDS字符串的类型.
#define SDS_TYPE_MASK 7 // 类型掩码 #define SDS_TYPE_BITS 3 #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T))); // 获取header头指针 #define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) // 获取header头指针 #define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) // 获取sdshdr5的长度,低三位保存了type
/*sds结构一共有五种Header定义,其目的是为了满足不同长度的字符串可以使用不同大小的Header,从而节省内存。 Header部分主要包含以下几个部分:len、alloc、flags其中 len:表示字符串真正的长度,不包含空终止字和alloc以及flag alloc:表示字符串的最大容量,不包含Header和最后的空终止字符和flag flag:只用了3位表示sds的type 就是表示header的类型*/ // 五种header类型,flags取值为0~4 #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
sdsnewlen函数
sds创建函数/* Create a new sds string with the content specified by the 'init' pointer * and 'initlen'. * If NULL is used for 'init' the string is initialized with zero bytes. * * The string is always null-termined (all the sds strings are, always) so * even if you create an sds string with: * * mystring = sdsnewlen("abc",3); * * You can print the string with printf() as there is an implicit \0 at the * end of the string. However the string is binary safe and can contain * \0 characters in the middle, as the length is stored in the sds header. */ sds sdsnewlen(const void *init, size_t initlen) { void *sh; sds s; // 根据initlen 求sds使用的结构体类型 char type = sdsReqType(initlen); //下面是我在测试的时候添加的打印字符串类型代码,源码中没有 int type_t = type; printf("sds.c------->sdsnewlen: the sds type is: [ %d ]\n", type_t); /* Empty strings are usually created in order to append. Use type 8 * since type 5 is not good at this. */ //type5 不再使用 而是直接使用type8 if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8; //sds使用的结构体的大小 int hdrlen = sdsHdrSize(type); unsigned char *fp; /* flags pointer. */ //flag的第三位表示sds使用的结构体 //分配内层 大小是sds结构体的大小: hdrlen + buf的大小initlen + '\0' sh = s_malloc(hdrlen+initlen+1); //init为null的时候 直接初始化sh内容 if (!init) memset(sh, 0, hdrlen+initlen+1); //sh 分配失败的情况直接返回null if (sh == NULL) return NULL; // s为数据部分的起始指针 指向buf地址指针 s = (char*)sh+hdrlen; //fp指向flag flag的低三位表示sds使用的结构体类型 fp = ((unsigned char*)s)-1; //根据sds使用的结构体类型 给结构体中的成员赋值 switch(type) { case SDS_TYPE_5: { // initlen << SDS_TYPE_BITS 把initlen的值保存到flag的高5位中去 低三位保存type的值 *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); // 与C字符串兼容 s[initlen] = '\0'; // 返回创建的sds字符串指针 return s; }
sdsMakeRoomFor函数
函数原型:sds sdsMakeRoomFor(sds s, size_t addlen)
说明:实现扩充已有sds的可用空间为指定的大小,扩充规则是:当addlen的长度小于1024*1024时,则申请的空间是2*(addlen+len),否则扩充为1024*1024大小。
返回值:扩充后的sds对象
/* Enlarge the free space at the end of the sds string so that the caller * is sure that after calling this function can overwrite up to addlen * bytes after the end of the string, plus one more byte for nul term. * * Note: this does not change the *length* of the sds string as returned * by sdslen(), but only the free buffer space we have. */ 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; //获取type int hdrlen; /* Return ASAP if there is enough space left. */ if (avail >= addlen) return s; //如果可用空间大于addlen直接返回旧的字符串 len = sdslen(s); //求sds的长度 sh = (char*)s-sdsHdrSize(oldtype); newlen = (len+addlen); if (newlen < SDS_MAX_PREALLOC) newlen *= 2; else newlen += SDS_MAX_PREALLOC; //根据newlen调整sds的type 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; //根据type获取sds使用的结构体的长度 hdrlen = sdsHdrSize(type); //若类型和原有类型一样,则采用realloc分配空间,否则重新分配采用malloc函数分配空间 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中的字符串拷贝到newsh指向的buf中去 s_free(sh);//释放旧的sh空间 s = (char*)newsh+hdrlen; //s指向重新分配的字符串 s[-1] = type; //指定newsds的type sdssetlen(s, len); //设置新的字符串的长度 } sdssetalloc(s, newlen); //设置新的newsh的分配的空间长度 return s; }
**sdscatlen **:sds提供了字符串的连接函数,用来连接两个字符串
//sds字符串连接 sds sdscatlen(sds s, const void *t, size_t len) { size_t curlen = sdslen(s); //扩展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 sdsempty(void); // 清空sds sds sdsdup(const sds s); // 复制字符串 sds sdsgrowzero(sds s, size_t len); // 扩展字符串到指定长度 sds sdscpylen(sds s, const char *t, size_t len); // 字符串的复制 sds sdscpy(sds s, const char *t); // 字符串的复制 sds sdscatfmt(sds s, char const *fmt, ...); //字符串格式化输出 sds sdstrim(sds s, const char *cset); //字符串缩减 void sdsrange(sds s, int start, int end); //字符串截取函数 void sdsupdatelen(sds s); //更新字符串最新的长度 void sdsclear(sds s); //字符串清空操作 void sdstolower(sds s); //sds字符转小写表示 void sdstoupper(sds s); //sds字符统一转大写 sds sdsjoin(char **argv, int argc, char *sep); //以分隔符连接字符串子数组构成新的字符串
sdsull2str 把一个long long的类型的数转成字符串
#include <iostream> using namespace std; int sdsll2str(char *s, long long value) { char *p, aux; unsigned long long v; size_t l; /* Generate the string representation, this method produces * an reversed string. */ v = (value < 0) ? -value : value; p = s; do { *p++ = '0'+(v%10); v /= 10; } while(v); if (value < 0) *p++ = '-'; /* Compute length and add null term. */ l = p-s; *p = '\0'; /* Reverse the string. */ p--; while(s < p) { aux = *s; *s = *p; *p = aux; s++; p--; } // std::cout << s << std::endl; return l; } int main() { char str[10]={0}; sdsll2str(str, 1234567); std::cout << str << std::endl; }
相关文章推荐
- 【redis源码分析】动态字符串--sds
- (一)redis源码学习之简单动态字符串(SDS)
- Redis源码学习——简单动态字符串SDS(Simple Dynamic String)
- redis源码分析之简单动态字符串sds
- 结合redis设计与实现的redis源码学习-2-SDS(简单动态字符串)
- redis源码分析(三)redis命令学习总结—string字符串
- Redis学习——SDS字符串源码分析
- redis源码分析笔记2- redis的数据类型-动态字符串sds
- Redis源码分析(五)——简单动态字符串(sds)
- Redis源码分析(四)-- sds字符串
- Redis源码阅读笔记(1)-- 动态字符串sds
- redis源码学习-sds字符串结构
- Redis源码剖析(十)简单动态字符串sds
- redis源码分析(七)、redis命令学习总结—Redis 有序集合(sorted set)
- Redis源码阅读笔记(1)——简单动态字符串sds实现原理
- redis源码分析(1)----字符串sds
- Redis源码学习之【动态字符串】
- redis源码分析(四)、redis命令学习总结—链表List
- Redis源码学习之【动态字符串】
- Redis源码分析二、Redis简单动态字符串