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;
}