Cadena de análisis de código fuente de Redis

Introducción

Aunque Redis está implementado por el lenguaje C, Redis no usa directamente la representación de cadena tradicional del lenguaje C. En su lugar, construye un tipo abstracto llamado cadena dinámica simple (SDS) y usa SDS como la representación de cadena predeterminada de Redis.

Definición de SDS

sds y sdshdr se definen en el archivo de encabezado sds.h como este

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


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


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

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

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

El buf en sdshdr usa una matriz vacía en lugar de un puntero porque la matriz vacía no ocupa espacio (sizeof (struct sdshdr) == 8) y es conveniente administrar la aplicación y la liberación de memoria.

Echemos un vistazo a las funciones de operación básicas específicas:

/*
 * 返回 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));
}

Ventajas de SDS:

1. Asignación previa de espacio

Cuando la API de SDS modifica una SDS y necesita expandir el espacio de la SDS, el programa no solo asignará el espacio necesario para la modificación a la SDS, sino que también asignará espacio adicional no utilizado para la SDS.

  • Si después de modificar la SDS, la longitud de la SDS (es decir, el valor del atributo len) será inferior a 1 MB, entonces el programa asigna un espacio no utilizado del mismo tamaño que el atributo len, lo que significa el valor del atributo len. El atributo SDSlen será el mismo que el valor del atributo libre. Si la longitud de la SDS se convierte en 13 bytes después de modificar el programa, el programa también asignará 13 bytes de espacio no utilizado, y la longitud real de la matriz buf de la SDS se convertirá en 13 + 13 + 1 = 27 bytes (1 adicional Los bytes se utilizan para almacenar caracteres nulos).
  • Si después de modificar la SDS, la longitud de la SDS (es decir, el valor del atributo len) será mayor o igual a 1 MB, entonces el programa asignará 1 MB de espacio no utilizado. Por ejemplo, si el valor de la longitud de SDS es de 30 MB después de la modificación, el programa asignará 1 MB de espacio no utilizado y la longitud real de la matriz de búfer de la SDS se convertirá en 30 MB + 30 MB + 1 byte.

2. Liberación de espacio inerte

La liberación de espacio diferido se usa para optimizar la operación de acortamiento de cadenas de SDS: cuando la API de SDS necesita acortar la cadena guardada por SDS, el programa no usa inmediatamente la reasignación de memoria para recuperar los bytes adicionales después de acortar, sino que usa el atributo libre para El número de estos bytes se registra y está a la espera de un uso futuro.

3. Seguridad binaria

SDS no solo puede guardar datos de texto, sino también datos binarios en cualquier formato, porque SDS usa el valor del atributo len en lugar de un carácter vacío para determinar si la cadena termina.

 

Para funciones específicas, lea el código fuente de redis src \ sds.c, el enlace de descarga del código fuente

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

Referencia: "Diseño e implementación de Redis"

Supongo que te gusta

Origin blog.csdn.net/u014608280/article/details/104734856
Recomendado
Clasificación