*字符串黑客技巧
Redis 字符串的实现包含在 sds.c 中(sds 代表简单动态字符串)。该实现作为独立库在 https://github.com/antirez/sds 提供。
在 sds.h 中声明的 C 结构 sdshdr 表示一个 Redis 字符串:
struct sdshdr {
long len;
long free;
char buf[];
};
buf 字符数组存储实际的字符串。
len 字段存储 buf 的长度。这使得获取 Redis 字符串的长度成为 O(1) 操作。
free 字段存储可供使用的额外字节数。
len 和 free 字段一起可以被视为持有 buf 字符数组的元数据。
*创建 Redis 字符串
在 sds.h 中定义了一种名为 sds 的新数据类型,作为字符指针的同义词:
typedef char *sds;
在 sds.c 中定义的 sdsnewlen 函数创建一个新的 Redis 字符串:
sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
#ifdef SDS_ABORT_ON_OOM
if (sh == NULL) sdsOomAbort();
#else
if (sh == NULL) return NULL;
#endif
sh->len = initlen;
sh->free = 0;
if (initlen) {
if (init) memcpy(sh->buf, init, initlen);
else memset(sh->buf,0,initlen);
}
sh->buf[initlen] = '\0';
return (char*)sh->buf;
}
记住 Redis 字符串是 struct sdshdr 类型的变量。但 sdsnewlen 返回一个字符指针!!
这是一个技巧,需要一些解释。
假设我使用 sdsnewlen 创建 Redis 字符串,如下所示:
sdsnewlen("redis", 5);
这会创建一个 struct sdshdr 类型的新变量,为 len 和 free 字段以及 buf 字符数组分配内存。
sh = zmalloc(sizeof(struct sdshdr)+initlen+1); // initlen 是 init 参数的长度。
sdsnewlen 成功创建 Redis 字符串后,结果类似于:
-----------
|5|0|redis|
-----------
^ ^
sh sh->buf
sdsnewlen 将 sh->buf 返回给调用者。
如果你需要释放由 sh 指向的 Redis 字符串怎么办?
你想要指针 sh,但你只有指针 sh->buf。
你能从 sh->buf 得到指针 sh 吗?
可以。指针算术。注意从上面的 ASCII 艺术图中,如果你从 sh->buf 减去两个 long 的大小,你就得到了指针 sh。
两个 long 的大小恰好就是 struct sdshdr 的大小。
看看 sdslen 函数,看看这个技巧是如何工作的:
size_t sdslen(const sds s) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
return sh->len;
}
知道了这个技巧,你可以轻松地浏览 sds.c 中的其余函数。
Redis 字符串的实现隐藏在一个只接受字符指针的接口后面。Redis 字符串的用户不需要关心它是如何实现的,可以将 Redis 字符串视为字符指针。