Redisソースコード分析-文字列

前書き

RedisはC言語で実装されていますが、RedisはC言語の従来の文字列表現を直接使用せず、代わりに、Simple Dynamic String(SDS)呼ばれる抽象型を構築し、Redisのデフォルトの文字列表現としてSDSを使用します。

SDSの定義

sdsとsdshdrは、次のようにsds.hヘッダーファイルで定義されます。

/*
 * 最大预分配长度
 */
#define SDS_MAX_PREALLOC (1024*1024)


/*指向sdshdr中的buf*/
typedef char *sds;


struct sdshdr {
    
    // buf 中已占用空间的长度
    //等于SDS所保存字符串的长度
    int len;

    // 记录buf数组中未使用空间的长度 
    int free;

    // 字节数组,用于保存字符串
    char buf[];
};

sdshdrのbufは、空の配列がスペースを占有せず(sizeof(struct sdshdr)== 8)、アプリケーションとメモリの解放を管理するのに便利なため、ポインタの代わりに空の配列を使用します。

特定の基本的な操作機能を見てみましょう。

/*
 * 返回 sds 实际保存的字符串的长度
 *
 * T = O(1)
 */
static inline size_t sdslen(const sds s) {
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    return sh->len;
}

/*
 * 返回 sds 可用空间的长度
 *
 * T = O(1)
 */
static inline size_t sdsavail(const sds s) {
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    return sh->free;
}

/*
 * 根据给定的初始化字符串 init 和字符串长度 initlen
 * 创建一个新的 sds
 *
 * 参数
 *  init :初始化字符串指针
 *  initlen :初始化字符串的长度
 *
 * 返回值
 *  sds :创建成功返回 sdshdr 相对应的 sds
 *        创建失败返回 NULL
 *
 * 复杂度
 *  T = O(N)
 */

sds sdsnewlen(const void *init, size_t initlen) {

    struct sdshdr *sh;

    // 根据是否有初始化内容,选择适当的内存分配方式
    // T = O(N)
    if (init) {
        // zmalloc 不初始化所分配的内存
        sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
    } else {
        // zcalloc 将分配的内存全部初始化为 0
        sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
    }

    // 内存分配失败,返回
    if (sh == NULL) return NULL;

    // 设置初始化长度
    sh->len = initlen;
    // 新 sds 不预留任何空间
    sh->free = 0;
    // 如果有指定初始化内容,将它们复制到 sdshdr 的 buf 中
    // T = O(N)
    if (initlen && init)
        memcpy(sh->buf, init, initlen);
    // 以 \0 结尾
    sh->buf[initlen] = '\0';

    // 返回 buf 部分,而不是整个 sdshdr
    return (char*)sh->buf;
}

/*
 * 创建并返回一个只保存了空字符串 "" 的 sds
 *
 * 返回值
 *  sds :创建成功返回 sdshdr 相对应的 sds
 *        创建失败返回 NULL
 *
 * 复杂度
 *  T = O(1)
 */
sds sdsempty(void) {
    return sdsnewlen("",0);
}


/*
 * 释放给定的 sds
 *
 * 复杂度
 *  T = O(N)
 */

void sdsfree(sds s) {
    if (s == NULL) return;
    zfree(s-sizeof(struct sdshdr));
}

/*
 * 在不释放 SDS 的字符串空间的情况下,
 * 重置 SDS 所保存的字符串为空字符串。
 *
 * 复杂度
 *  T = O(1)
 */
void sdsclear(sds s) {

    // 取出 sdshdr
    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));

    // 重新计算属性
    sh->free += sh->len;
    sh->len = 0;

    // 将结束符放到最前面(相当于惰性地删除 buf 中的内容)
    sh->buf[0] = '\0';
}

/*
 * 对 sds 中 buf 的长度进行扩展,确保在函数执行之后,
 * buf 至少会有 addlen + 1 长度的空余空间
 * (额外的 1 字节是为 \0 准备的)
 *
 * 返回值
 *  sds :扩展成功返回扩展后的 sds
 *        扩展失败返回 NULL
 *
 * 复杂度
 *  T = O(N)
 */
sds sdsMakeRoomFor(sds s, size_t addlen) {

    struct sdshdr *sh, *newsh;

    // 获取 s 目前的空余空间长度
    size_t free = sdsavail(s);

    size_t len, newlen;

    // s 目前的空余空间已经足够,无须再进行扩展,直接返回
    if (free >= addlen) return s;

    // 获取 s 目前已占用空间的长度
    len = sdslen(s);
    sh = (void*) (s-(sizeof(struct sdshdr)));

    // s 最少需要的长度
    newlen = (len+addlen);

    // 根据新长度,为 s 分配新空间所需的大小
    if (newlen < SDS_MAX_PREALLOC)
        // 如果新长度小于 SDS_MAX_PREALLOC 
        // 那么为它分配两倍于所需长度的空间
        newlen *= 2;
    else
        // 否则,分配长度为目前长度加上 SDS_MAX_PREALLOC
        newlen += SDS_MAX_PREALLOC;
    // T = O(N)
    newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);

    // 内存不足,分配失败,返回
    if (newsh == NULL) return NULL;

    // 更新 sds 的空余长度
    newsh->free = newlen - len;

    // 返回 sds
    return newsh->buf;
}

/*
 * 回收 sds 中的空闲空间,
 * 回收不会对 sds 中保存的字符串内容做任何修改。
 *
 * 返回值
 *  sds :内存调整后的 sds
 *
 * 复杂度
 *  T = O(N)
 */

sds sdsRemoveFreeSpace(sds s) {
    struct sdshdr *sh;

    sh = (void*) (s-(sizeof(struct sdshdr)));

    // 进行内存重分配,让 buf 的长度仅仅足够保存字符串内容
    // T = O(N)
    sh = zrealloc(sh, sizeof(struct sdshdr)+sh->len+1);

    // 空余空间为 0
    sh->free = 0;

    return sh->buf;
}

/*
 * 返回给定 sds 分配的内存字节数
 *
 * 复杂度
 *  T = O(1)
 */

size_t sdsAllocSize(sds s) {
    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));

    return sizeof(*sh)+sh->len+sh->free+1;
}

/*
 * 将 sds 扩充至指定长度,未使用的空间以 0 字节填充。
 *
 * 返回值
 *  sds :扩充成功返回新 sds ,失败返回 NULL
 *
 * 复杂度:
 *  T = O(N)
 */
sds sdsgrowzero(sds s, size_t len) {
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    size_t totlen, curlen = sh->len;

    // 如果 len 比字符串的现有长度小,
    // 那么直接返回,不做动作
    if (len <= curlen) return s;

    // 扩展 sds
    // T = O(N)
    s = sdsMakeRoomFor(s,len-curlen);
    // 如果内存不足,直接返回
    if (s == NULL) return NULL;

    /* Make sure added region doesn't contain garbage */
    // 将新分配的空间用 0 填充,防止出现垃圾内容
    // T = O(N)
    sh = (void*)(s-(sizeof(struct sdshdr)));
    memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */

    // 更新属性
    totlen = sh->len+sh->free;
    sh->len = len;
    sh->free = totlen-sh->len;

    // 返回新的 sds
    return s;
}

/*
 * 将长度为 len 的字符串 t 追加到 sds 的字符串末尾
 *
 * 返回值
 *  sds :追加成功返回新 sds ,失败返回 NULL
 *
 * 复杂度
 *  T = O(N)
 */

sds sdscatlen(sds s, const void *t, size_t len) {
    
    struct sdshdr *sh;
    
    // 原有字符串长度
    size_t curlen = sdslen(s);

    // 扩展 sds 空间
    // T = O(N)
    s = sdsMakeRoomFor(s,len);

    // 内存不足?直接返回
    if (s == NULL) return NULL;

    // 复制 t 中的内容到字符串后部
    // T = O(N)
    sh = (void*) (s-(sizeof(struct sdshdr)));
    memcpy(s+curlen, t, len);

    // 更新属性
    sh->len = curlen+len;
    sh->free = sh->free-len;

    // 添加新结尾符号
    s[curlen+len] = '\0';

    // 返回新 sds
    return s;
}

/*
 * 将给定字符串 t 追加到 sds 的末尾
 * 
 * 返回值
 *  sds :追加成功返回新 sds ,失败返回 NULL
 *
 * 复杂度
 *  T = O(N)
 */
sds sdscat(sds s, const char *t) {
    return sdscatlen(s, t, strlen(t));
}


/*
 * 将另一个 sds 追加到一个 sds 的末尾
 * 
 * 返回值
 *  sds :追加成功返回新 sds ,失败返回 NULL
 *
 * 复杂度
 *  T = O(N)
 */

sds sdscatsds(sds s, const sds t) {
    return sdscatlen(s, t, sdslen(t));
}

/*
 * 将字符串 t 的前 len 个字符复制到 sds s 当中,
 * 并在字符串的最后添加终结符。
 *
 * 如果 sds 的长度少于 len 个字符,那么扩展 sds
 *
 * 复杂度
 *  T = O(N)
 *
 * 返回值
 *  sds :复制成功返回新的 sds ,否则返回 NULL
 */

sds sdscpylen(sds s, const char *t, size_t len) {

    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));

    // sds 现有 buf 的长度
    size_t totlen = sh->free+sh->len;

    // 如果 s 的 buf 长度不满足 len ,那么扩展它
    if (totlen < len) {
        // T = O(N)
        s = sdsMakeRoomFor(s,len-sh->len);
        if (s == NULL) return NULL;
        sh = (void*) (s-(sizeof(struct sdshdr)));
        totlen = sh->free+sh->len;
    }

    // 复制内容
    // T = O(N)
    memcpy(s, t, len);

    // 添加终结符号
    s[len] = '\0';

    // 更新属性
    sh->len = len;
    sh->free = totlen-len;

    // 返回新的 sds
    return s;
}

/*
 * 将字符串复制到 sds 当中,
 * 覆盖原有的字符。
 *
 * 如果 sds 的长度少于字符串的长度,那么扩展 sds 。
 *
 * 复杂度
 *  T = O(N)
 *
 * 返回值
 *  sds :复制成功返回新的 sds ,否则返回 NULL
 */

sds sdscpy(sds s, const char *t) {
    return sdscpylen(s, t, strlen(t));
}

SDSの利点:

1.スペースの事前割り当て

SDS APIがSDSを変更し、SDSのスペースを拡張する必要がある場合、プログラムは変更に必要なスペースをSDSに割り当てるだけでなく、SDSに追加の未使用スペースを割り当てます。

  • SDSを変更した後、SDSの長さ(つまり、len属性の値)が1MB未満になる場合、プログラムはlen属性と同じサイズの未使用スペースを割り当てます。これは、SDSの値を意味します。 SDSlen属性は、free属性の値と同じになります。プログラムの変更後にSDSのlenが13バイトになると、プログラムは13バイトの未使用スペースも割り当て、SDSのbuf配列の実際の長さは13 + 13 + 1 = 27バイトになります(追加の1バイトはヌル文字を格納するために使用されます)。
  • SDSを変更した後、SDSの長さ(つまり、len属性の値)が1MB以上になる場合、プログラムは1MBの未使用スペースを割り当てます。たとえば、変更後のSDSのlenの値が30MBの場合、プログラムは1MBの未使用スペースを割り当て、SDSのbuf配列の実際の長さは30MB + 30MB + 1byteになります。

2.不活性空間の解放

レイジースペースリリースは、SDSの文字列短縮操作を最適化するために使用されます。SDSAPIがSDSによって保存された文字列を短縮する必要がある場合、プログラムは、短縮後に余分なバイトを再利用するためにメモリ再割り当てをすぐに使用しませんが、free属性を使用してこれらのバイト数が記録され、将来の使用を待機しています。

3.バイナリセキュリティ

SDSは、テキストデータだけでなく、任意の形式のバイナリデータも保存できます。これは、SDSが空の文字の代わりにlen属性の値を使用して、文字列が終了するかどうかを判断するためです。

 

特定の機能については、redisソースコードsrc \ sds.c、ソースコードダウンロードリンクをお読みください

https://download.csdn.net/download/u014608280/12234598

参照:「Redisの設計と実装」

おすすめ

転載: blog.csdn.net/u014608280/article/details/104734856