"Projeto e Implementação do Redis" -- String Dinâmica Simples

string dinâmica simples

O Redis cria um tipo abstrato de string dinâmica (string dinâmica simples, SDS) e usa esse tipo como a representação de caractere padrão do Redis,
enquanto os literais de string da linguagem C são usados ​​apenas como algumas strings que não precisam ser manipuladas. modificar, se for o local para imprimir o log.

Definição de SDS

struct sdshdr {
    // 记录buf数组中已使用字符的数量,等于SDS保存字符串的长度
    int len;
    // 记录的是buf数组中未使用的字节的数量
    int free;
    // 字节数组,用于保存字符串
    char buf[];
};
复制代码

O SDS segue a convenção de terminação nula para strings C. Seguir essa convenção permite que o SDS reutilize diretamente algumas funções em strings C.

Diferença entre SDS e string c

obter o comprimento da string

A string c não registra as informações de comprimento da string em si, então se você quiser obter o comprimento da string c, você precisa realizar uma operação de travessia. A complexidade desta operação é O(N). Para SDS, é O comprimento do próprio SDS, se você quiser obter as informações de comprimento da string, você só precisa de complexidade O(1)

estouro de buffer

Como a string C não registra seu próprio comprimento, é fácil causar estouro de buffer ao modificar a string. Por exemplo, existem duas strings adjacentes S1, S2, S1="Redis" na memória, S2="MongoDB"

image.pngQuando a operação extract(S1, "Cluster") for chamada, S1 será modificado para: Redis Cluster, mas se espaço suficiente não for alocado para S1 antes que o método seja chamado, os dados de S1 transbordarão para o espaço onde S2 está localizado , o que faz com que S2 seja modificado.
Ao contrário da string C, o SDS verificará primeiro se o espaço SDS atende ao conteúdo modificado ao modificá-lo. Caso contrário, ele expandirá automaticamente o espaço para o tamanho necessário e, em seguida, executará a operação de modificação real.

O número de realocações de memória causadas pela modificação da string

Como a string C não registra seu próprio comprimento, para uma string C contendo N caracteres, ela armazena uma matriz de caracteres de comprimento N+1 na parte inferior (um espaço extra é usado para armazenar uma string vazia) ). Devido a tal associação, uma operação de realocação de memória é sempre necessária cada vez que uma string C é aumentada ou reduzida.

  • 如果是增加字符的操作,那么在这个操作的之前需要通过重新分配内存来扩展底层数组空间的大小,不然就会产生缓冲的溢出
  • 如果是缩短字符串的操作,那么在这个操作之后就需要通过内存分配来释放不再使用的那部分的空间,如果忘记了就会产生内存的泄漏。

但Redis做为一个数据库,经常被使用在一些速度快,数据被频繁修改的场景里面,如果每次对于字符串的操作都需要进行一个内存的重分配的操作这是需要占用大量的时间的,且对性能造成影响。
所以为了避免这种缺陷,基于SDS的结构,SDS通过未使用空间解除了字符串长度与底层数组空间的关系:在SDS的底层,buf数组的长度就并不一定是字符串的长度加1,数组里面可以包含没有使用的字节,而这些字节的长度就由SDS的fee属性来记录。
基于SDS的未使用空间,SDS实现了空间预分配和惰性空间释放策略:

  • 空间预分配,这个操作简而言之就是当SDS进行修改需要扩张的时候,程序不仅会为SDS分配修改所需要的空间,同时也会为SDS分配额外的未使用空间。
    • 如果SDS进行修改之后,SDS的长度(len属性)如果小于1MB,那么程序分配和len同样大小的未使用空间,这个时候len属性将会与fee属性的值一样。
    • 如果SDS进行修改之后,SDS的长度大于等于1MB,那么程序会分配1MB的未使用空间。
  • 惰性空间释放,当SDS保存的字符串缩短的时候,程序并不会马上进行内存的重分配来回收缩短之后多出来的字节,而是使用fee这个属性将这些字节数量记录下来,并等待使用。

二进制安全

C字符串中字符必须符合某一种规则(比如ASCII),并且除了字符串的末尾之外,字符串里面不能包含空字符,否则最先被程序读入的空字符会被误认为是字符串结尾。而这些限制使得C字符串只能保存文本内容。
但Redis需要的不仅仅是对文本内容的保存场景,有时候也需要对一些二进制数据保存的场景,为了使得Redis适用于不同的场景。SDS的所有API操作都是二进制安全的,所有SDS的API操作都会以二进制的方式来处理SDS存放在buf数组里面的数据,程序并不会对其中的数据做任何的限制,过滤和假设。数据被写入的时候是什么样的,它被读取的时候就是什么样的。

总结

  • Redis只会使用C字符串做字面量,在大多数的情况下Redis使用SDS作为字符串的表示。
  • 比起C字符串,SDS有以下的优点
    • 常数复杂度获取字符串的长度
    • 杜绝了缓冲区的溢出
    • 减少了修改字符串长度所需要的内存分配次数
    • 二进制安全
    • 兼容部分C字符串的函数

补充

Redis源码中关于SDS结构定义:

struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
复制代码

Acho que você gosta

Origin juejin.im/post/7079779869168500744
Recomendado
Clasificación