字符串内部

Redis 字符串原始实现的指南

Redis 开源版本

注意:本文档由 Redis 创建者 Salvatore Sanfilippo 在 Redis 开发早期(约 2010 年)撰写。自 Redis 2.6 起,虚拟内存已被弃用,因此本文档仅供历史参考。

Redis 字符串的实现包含在 sds.c 中(sds 代表 Simple Dynamic Strings)。该实现作为一个独立的库,可在 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 字段存储可用的额外字节数。

lenfree 字段一起可以被视为存储 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 的新变量,为 lenfree 字段以及 buf 字符数组分配内存。

sh = zmalloc(sizeof(struct sdshdr)+initlen+1); // initlen is length of init argument.

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 的 sizeof 正好是 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 字符串视为字符指针。

评价此页
返回顶部 ↑