redis之sds(simple dynamic string)阅读笔记6-sds之可变参数
redis之sds(simple dynamic string)阅读笔记6-sds之可变参数 ********************************************************************** 函数sdscatvprintf接收可变参数 /* Like sdscatprintf() but gets va_list instead of being variadic. */ sds sdscatvprintf(sds s, const char *fmt, va_list ap) { va_list cpy; char staticbuf[1024], *buf = staticbuf, *t; size_t buflen = strlen(fmt)*2; /* We try to start using a static buffer for speed. * If not possible we revert to heap allocation. */ 为了提高速度,我们采用了一个静态的空间用作存储(可变参数的值) 在静态内存中间不够的情况下,我们才采用动态分配堆内存 if (buflen > sizeof(staticbuf)) { // 超过我们准备的内存空间 buf = s_malloc(buflen); //那就需要新分配内存空间 if (buf == NULL) return NULL; } else { buflen = sizeof(staticbuf); //返回我们准备空间的大小 } /* Try with buffers two times bigger every time we fail to * fit the string in the current buffer size. */ //当我们遇到在当前空间存放字符串失败的情况下,就每次尝试使用之前两倍大小的空间来存放 while(1) { buf[buflen-2] = '\0'; //在倒数第二的位置放置一个哨兵(如果这个哨兵被覆盖,那么需要拷贝的字符串至少长度为buflen-1或者更长) va_copy(cpy,ap); //用参数ap初始化cpy vsnprintf(buf, buflen, fmt, cpy); // 将可变参数中最多拷贝buflen-1的字节到buf中 va_end(cpy);//释放cpy if (buf[buflen-2] != '\0') { //如果哨兵没有被覆盖,那么长度就够了,否则需要重新申请内存来存放变参中的字符串, //但是这里有一种临界情况,就是刚好长度是buflen-1,即刚覆盖我们的哨兵,这种情况也是需要扩充内存的,事实上不需要 if (buf != staticbuf) s_free(buf); //是新分配的内存,不是我们预定义使用的内存staticbuf,需要释放,否则会有没有回收的内存碎片 buflen *= 2;//通过翻倍的方式扩充内存,试探是否够输入参数长度 buf = s_malloc(buflen); if (buf == NULL) return NULL; continue; //继续下次尝试 } break;//如果分配内存长度已经够存放输入可变参数了,那么循环就结束了 } /* Finally concat the obtained string to the SDS string and return it. */ t = sdscat(s, buf); //将新获得的字符串连接到原字符串之后 if (buf != staticbuf) s_free(buf); //记得释放动态分配内存空间,免得留下孤魂野鬼 return t; //返回得到的新字符串 } ********************************************************************** 函数sdscatprintf /* Append to the sds string 's' a string obtained using printf-alike format * specifier. 使用类似printf格式的方法得到的字符串,然后将这个字符串添加到sds字符串‘s’后面 * After the call, the modified sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. 进过这函数的调用,原sds字符串🏆释放,所有的引用必须用函数调用返回的新指针 * Example: 示例如下: * s = sdsnew("Sum is: "); //创建新sds字符串 * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b). //将格式化输入的参数添加到sds字符串后面 * * Often you need to create a string from scratch with the printf-alike * format. When this is the need, just use sdsempty() as the target string: 通常如果你需要用格式化的方法创建一个字符串进行连接,你可以使用函数sdsempty(产生的字符串)作为目标字符串 * s = sdscatprintf(sdsempty(), "... your format ...", args); */ sds sdscatprintf(sds s, const char *fmt, ...) { va_list ap; char *t; va_start(ap, fmt); // 初始化参数ap t = sdscatvprintf(s,fmt,ap); //调用sdscatvprintf 获取格式化字符串 va_end(ap); // 释放参数ap return t; } ********************************************************************** 函数sdscatfmt 是自己写的处理可变参数的函数 /* This function is similar to sdscatprintf, but much faster as it does * not rely on sprintf() family functions implemented by the libc that * are often very slow. Moreover directly handling the sds string as * new data is concatenated provides a performance improvement. 这个函数类似sdscatprintf,但是比sdscatprintf要快,主要是因为它不依赖于有库实现的函数家族sprintf, 这类库函数通常比较慢。进一步,直接像新数据串联一样处理sds字符串提供了显著的性能改善 * However this function only handles an incompatible subset of printf-alike * format specifiers: 不过这个函数只是类printf格式方法的一个不兼容子集. * %s - C String * %S - SDS string * %i - signed int * %I - 64 bit signed integer (long long, int64_t) * %u - unsigned int * %U - 64 bit unsigned integer (unsigned long long, uint64_t) * %% - Verbatim "%" character. // %本身 */ sds sdscatfmt(sds s, char const *fmt, ...) { size_t initlen = sdslen(s); const char *f = fmt; long i; va_list ap; /* To avoid continuous reallocations, let's start with a buffer that * can hold at least two times the format string itself. It's not the * best heuristic but seems to work in practice. */ 为了避免连续分配内存,我们初始化的时候就分配一个格式化字符串两倍长的内存空间, 这个不是最优的分配内存启发式算法,但是在实际应用在足够了 s = sdsMakeRoomFor(s, initlen + strlen(fmt)*2);//分配两倍长的格式化字符串内存空间 va_start(ap,fmt); // 初始化ap f = fmt; /* Next format specifier byte to process. */ i = initlen; /* Position of the next byte to write to dest str. */ while(*f) { //遍历格式化字符串的每个字符 char next, *str; size_t l; long long num; unsigned long long unum; /* Make sure there is always space for at least 1 char. */ 至少需要一个字符的空间,用来存放遍历的格式化字符串的一个字符 if (sdsavail(s)==0) { s = sdsMakeRoomFor(s,1); } switch(*f) { //对每个字符进行判断 case '%': // 如果遇到% 说明遇到格式了 next = *(f+1); //需要继续取下一个字符判断是什么格式 f++;//又取了一个,所以需要继续往下+1 switch(next) { case 's': case 'S': str = va_arg(ap,char*); // 字符类型参数, 赋值给str l = (next == 's') ? strlen(str) : sdslen(str); // C or SDS,调用不同的长度函数 if (sdsavail(s) < l) { //如果剩余空间不够,那么需要分配足够的空间 s = sdsMakeRoomFor(s,l); } memcpy(s+i,str,l); //将长度为l(字母l)参数内容str拷贝sds字符串中 sdsinclen(s,l); // 将sds字符串长度修改 i += l; //变化i指向位置,供下次拷贝字符使用,不会覆盖 break; case 'i': case 'I': if (next == 'i') num = va_arg(ap,int); else num = va_arg(ap,long long); { char buf[SDS_LLSTR_SIZE]; l = sdsll2str(buf,num); // 长整形转化字符串函数 if (sdsavail(s) < l) { s = sdsMakeRoomFor(s,l); } memcpy(s+i,buf,l); sdsinclen(s,l); i += l; } break; case 'u': case 'U': if (next == 'u') unum = va_arg(ap,unsigned int); else unum = va_arg(ap,unsigned long long); { char buf[SDS_LLSTR_SIZE]; l = sdsull2str(buf,unum); // 无符号长整形转化字符串函数 if (sdsavail(s) < l) { s = sdsMakeRoomFor(s,l); } memcpy(s+i,buf,l); sdsinclen(s,l); i += l; } break; default: /* Handle %% and generally %<unknown>. */ //如果后面的字符也是%,那就当做%字符处理 s[i++] = next; sdsinclen(s,1); break; } break; default: //非%的情况,直接添加到结尾即可 s[i++] = *f; sdsinclen(s,1); break; } f++; //取下一个继续循环 } va_end(ap); //释放可变参数列表 /* Add null-term */ s[i] = '\0'; //设置结尾符/0 return s; }