最近在阅读 redis 代码, 觉得 如何阅读 Redis 源码 提供了的阅读顺序还不错, 因为之前有读过 lua 代码, 也有一点经验, 感觉作者建议的顺序还是比较合理的. 所以先从 redis 的内部字符串的实现开始看起, 在阅读过程中看到一个我个人认为很赞的技巧. 先贴代码:
(不得不说, redis 的代码注释真是详尽到了极致 … lua的注释很本没法比啊)
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 = zmalloc(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';
va_copy(cpy,ap);
vsnprintf(buf, buflen, fmt, cpy);
va_end(cpy);
if (buf[buflen-2] != '\0') {
if (buf != staticbuf) zfree(buf);
buflen *= 2;
buf = zmalloc(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) zfree(buf);
return t;
}
sds 是 redis 的内部字符串类型, 其实就是 char * 的 typedef, 区别在其存取方式上, 这里把它当成 char * 就可以了.
几乎从函数名我们就能知道这个函数功能是将 字符串 s 和 格式化后的字符串 拼接在一起, 并返回这个新的字符串.
要实现这个功能不难, 但有一个问题需要考虑, 因为 ‘格式化的字符串’ 的长度你是不知道的, 所以我们需要用到 buffer, 但 buffer 应该选多大, 是直接放在函数栈上还是放在堆上? 这是个问题.
redis 采用的办法是:
- 先在栈上开一个 1024 字节 staticbuf 数组;
- 估计一下可能的 ‘格式化字符串’ 的大小 ( strlen(fmt) *2 );
- 如果 ‘格式化字符串’ 长度大于 staticbuf 数组长度, 那么就用 zmalloc (可以看作 malloc) 在堆上开一块相应大小的内存;
- 如果实际的 ‘格式化字符串’ 比估计大怎么办? 先 zfree (可以看作 free) 之前在堆上开的内存, 然后再在堆上开一块 2倍于之前大小的内存;
- 如果还是不够, 重复 4;
- 最后记得释放堆内存 (如果开辟了的话), 防止内存泄露.