Resumen de la estructura de datos redis

Tabla de contenido

1, tipo de cadena

2, tipo de lista

3. tipo de hash

dictEntrada

dictado

dictar

4. Tipo de colección

Comparación de skiplist con árbol balanceado y tabla hash


Redis puede almacenar cinco estructuras de datos: String (cadena), List (lista), Set (conjunto), Hash (hash), Zset (conjunto ordenado). Los comandos como del, type y rename son comunes.Además, tenga en cuenta que estas estructuras se almacenan en una estructura de datos clave. Eso es como se dice:

El objeto Redis está representado por la estructura redisObject:
typedef struct redisObject {     unsigned type:4; // El tipo del objeto, incluidos /* Tipos de objetos */     codificación sin firmar:4; // En la parte inferior, para ahorrar espacio , se puede usar un tipo de datos Diferentes métodos de almacenamiento     unsigned lru:REDIS_LRU_BITS; /* lru time (relativo a server.lruclock) */     int refcount; // recuento de referencias     void *ptr; } robj;





Tenga en cuenta que lo anterior se refiere principalmente a redis3.2, y la última versión de redis5 es básicamente la misma, pero no se pueden encontrar algunos archivos y puede haber cambios.

1, tipo de cadena

 

El tamaño máximo es 512 M. El tipo String se almacena como una estructura a través de int y SDS (cadena dinámica simple). Int se usa para almacenar datos enteros, y sds se usa para almacenar byte/cadena y datos de coma flotante.

En el código fuente de redis [sds.h], la estructura de sds es la siguiente:

typedef char *sds;

La rama redis3.2 presenta cinco tipos de sdshdr. El propósito es cumplir con los requisitos de usar diferentes tamaños de encabezados para cadenas de diferentes longitudes, ahorrando así memoria. Al crear un sds cada vez, juzgue qué tipo de sdshdr debe seleccionarse según a la longitud real de los sds.Diferentes tipos de sdshdr ocupan diferentes espacios de memoria. Subdividirlo de esta manera puede ahorrar una gran cantidad de sobrecarga de memoria innecesaria.La siguiente es la definición de sdshdr de 3.2, que es la misma en la versión 5.0.7:

 

typedef char *sds;

/* Nota: sdshdr5 nunca se usa, solo accedemos directamente al byte de banderas.
 * Sin embargo, está aquí para documentar el diseño de las cadenas SDS de tipo 5. */
struct __attribute__ ((__packed__)) sdshdr5 {     banderas de char sin firmar; /* 3 lsb de tipo y 5 msb de longitud de cadena */     char buf[]; }; struct __attribute__ ((__packed__)) sdshdr8   8 significa que la longitud máxima de la cadena es 2^8, y la longitud es 255   {     uint8_t len; /* used */     significa La longitud del sds actual (la unidad es un byte)     uint8_t alloc; /* excluyendo el encabezado y el terminador nulo */   Indica el tamaño del espacio de memoria asignado para sds (la unidad es un byte)     banderas de caracteres sin firmar; /* 3 lsb de tipo, 5 bits sin usar */  Use un byte para representar el tipo actual de sdshdr, porque hay cinco tipos de sdshdr, por lo que se requieren al menos 3 bits para representar     char buf[];    la ubicación de almacenamiento real de sds








};
estructura __atributo__ ((__empaquetado__)) sdshdr16 {     uint16_t len; /* usado */     uint16_t alloc; /* excluyendo el encabezado y el terminador nulo */     banderas de caracteres sin firmar; /* 3 lsb de tipo, 5 bits sin usar */     char buf[]; }; estructura __atributo__ ((__empaquetado__)) sdshdr32 {     uint32_t len; /* usado */     uint32_t alloc; /* excluyendo el encabezado y el terminador nulo */     banderas de caracteres sin firmar; /* 3 lsb de tipo, 5 bits sin usar */     char buf[]; }; estructura __atributo__ ((__empaquetado__)) sdshdr64 {     uint64_t len; /* usado */     uint64_t alloc; /* excluyendo el encabezado y el terminador nulo */














    banderas char sin firmar; /* 3 lsb de tipo, 5 bits sin usar */
    char buf[];
};

icono de SD:

La estructura de datos de tipo cadena se puede utilizar para: buzones de usuario, imágenes, etc.

Resumen de la operación:

127.0.0.1:6379> establecer un hombre

DE ACUERDO

127.0.0.1:6379> obtener un

"hombre"

127.0.0.1:6379> del a

(entero) 1

127.0.0.1:6379> obtener un

(nulo)

 

2, tipo de lista

 

La lista se implementa con una lista doblemente enlazada, por lo que es más rápido agregar en ambos extremos y la complejidad de tiempo es O(1).

Antes de redis 3.2, el objeto de valor de tipo List se implementaba internamente mediante la lista enlazada o ziplist. Cuando la cantidad de elementos en la lista y la longitud de un solo elemento eran relativamente pequeños, Redis usaba ziplist (lista comprimida) para implementarlo para reducir el uso de la memoria. De lo contrario, se utilizará una estructura de lista enlazada (lista doblemente enlazada). Después de redis 3.2, se usa una estructura de datos llamada lista rápida para almacenar la lista, y la lista rápida implementa la capa inferior de la lista.

Estos dos métodos de almacenamiento tienen ventajas y desventajas. La lista enlazada bidireccional realiza operaciones push y pop en ambos extremos de la lista enlazada. La complejidad de insertar nodos es relativamente baja, pero la sobrecarga de memoria es relativamente grande; ziplist se almacena en un memoria continua, por lo que la eficiencia de almacenamiento es muy alta Alta, pero tanto la inserción como la eliminación requieren una aplicación y liberación frecuentes de memoria ;

 

Quicklist sigue siendo una lista doblemente enlazada, pero cada nodo de la lista es un ziplist , que en realidad es una combinación de linkedlist y ziplist. Cada nodo en quicklist ziplist puede almacenar varios elementos de datos. El archivo en el código fuente es [quicklist.c ], explicado en la primera línea del código fuente: Lista doblemente enlazada de ziplists significa una lista doblemente enlazada compuesta de ziplist;

 

Operaciones básicas, lpush, rpush, lpop, rpop, lrange; lrange list-key 0 -1 significa buscar todo.

lista rápida:

typedef struct lista rápida {     quicklistNode *head;     QuickListNode * cola;     cuenta larga sin signo; /* recuento total de todas las entradas en todas las ziplists */     longitud larga sin firmar; /* número de QuicklistNodes */     int fill : 16; /* factor de relleno para nodos individuales */     unsigned int compress : 16; /* profundidad de los nodos finales que no se comprimirán; 0=desactivado */ } lista rápida;






Nodo de lista rápida:

 

typedef struct lista rápidaNodo {

struct quicklistNode *anterior;

estructura rapidlistNode *siguiente;

carácter sin signo *zl;

sin firmar int sz; /* tamaño de la lista zip en bytes */

cuenta int sin firmar: 16; /* recuento de elementos en ziplist */

codificación int sin firmar: 2; /* RAW==1 o LZF==2 */

contenedor int sin firmar: 2; /* NINGUNO==1 o ZIPLIST==2 */

int sin firmar recomprimir: 1; /* ¿este nodo estaba comprimido anteriormente? */

int sin firmar intentó_comprimir: 1; /* el nodo no puede comprimir; demasiado pequeña */

int extra sin firmar: 10; /* más bits para robar para uso futuro */

} lista rápidaNodo;

 

El diseño general de la ziplist es el siguiente:

<zlbytes> <zltail> <zllen> <entrada> <entrada> ... <entrada> <zlend>

 

El almacenamiento de la lista se muestra en la siguiente figura:

 

Escena de uso: ligeramente

Sección de operación:

127.0.0.1:6379> presione un 2

(entero) 1

127.0.0.1:6379> presione un 3

(entero) 2

127.0.0.1:6379> presione un 1

(entero) 3

127.0.0.1:6379> lrange a 0 -1

1) "3"

2) "2"

3) "1"

127.0.0.1:6379> lindex es 1

"2"

127.0.0.1:6379> lpop un

"3"

127.0.0.1:6379> rpop un

"1"

127.0.0.1:6379> lrange a 0 -1

1) "2"

 

3. tipo de hash

 

Conceptualmente, el tipo de hash es un mapa. Se realiza a través de hashtable o ziplist. Cuando la cantidad de datos es pequeña, use ziplist, y cuando la cantidad de datos sea grande, use hashtable. ¿Qué tan pequeño es usar ziplist? Echa un vistazo al código fuente:

 

/* Comprobamos la longitud de una serie de objetos para ver si necesitamos convertir una
 * ziplist en un hash real. Tenga en cuenta que solo comprobamos los objetos codificados con cadenas
 * ya que su longitud de cadena se puede consultar en tiempo constante. */  
Esta pieza de código Se usa para juzgar si convertir la lista zip en un hash real, preste atención para verificar solo el objeto de codificación de caracteres
void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {     int i;

    if (o->encoding != OBJ_ENCODING_ZIPLIST) devuelve;  
Si la codificación de o no es OBJ_ENCODING_ZIPLIST, no se procesará

    for (i = inicio; i <= fin; i++) {         if (sdsEncodedObject(argv[i]) &&             sdslen(argv[i]->ptr) > server.hash_max_ziplist_value)         { hashTypeConvert(o, OBJ_ENCODING_HT);             romper;         } }     }



           



转换还有一段代码:
/* Agregar un nuevo campo, sobrescribir el antiguo con el nuevo valor si ya existe.
 * Devuelve 0 al insertar y 1 al actualizar.
 *新增一个字段,如果存在就覆盖
 * De forma predeterminada, las cadenas SDS de clave y valor se copian si es necesario, por lo que
 * la persona que llama conserva la propiedad de las cadenas pasadas. Sin embargo, este comportamiento
 * puede efectuarse pasando las banderas apropiadas (posiblemente OR-ed bit a bit):
 * 
 * HASH_SET_TAKE_FIELD -- La propiedad del campo SDS pasa a la función.
 * HASH_SET_TAKE_VALUE: la propiedad del valor SDS pasa a la función.
 *
 * Cuando se usan las banderas, la persona que llama no necesita liberar la(s)
 cadena(s) SDS pasada(s). Depende de la función usar la cadena para crear una nueva
 * entrada o para liberar la cadena SDS antes de volver a la persona que llama.
 *
 * HASH_SET_COPY corresponde a que no se pasan banderas, y significa la
 semántica predeterminada * de copiar los valores si es necesario.
 *
 */
#define HASH_SET_TAKE_FIELD (1<<0)
#define HASH_SET_TAKE_VALUE (1<<1)
#define HASH_SET_COPY 0
int hashTypeSet(robj *o, sds field, sds value, int flags) {     int update = 0;

    if (o->codificación == OBJ_ENCODING_ZIPLIST) {         char sin firmar *zl, *fptr, *vptr;

        zl = o->ptr;
        fptr = ziplistIndex(zl, ZIPLIST_HEAD);
        if (fptr != NULL) {             fptr = ziplistFind(fptr, (carácter sin firmar*)campo, sdslen(campo), 1);             if (fptr != NULL) {                 /* Agarra el puntero al valor (fptr apunta al campo) */                 vptr = ziplistNext(zl, fptr);                 serverAssert(vptr != NULL);                 actualizar = 1;





                /* Eliminar valor */
                zl = ziplistDelete(zl, &vptr);

                /* Insertar nuevo valor */
                zl = ziplistInsert(zl, vptr, (char sin firmar*)valor,
                        sdslen(valor));
            }
        }

        if (!update) {             /* Inserta un nuevo par de campo/valor en la cola de la ziplist */             zl = ziplistPush(zl, (unsigned char*)field, sdslen(field),                     ZIPLIST_TAIL);             zl = ziplistPush(zl, (caracter sin firmar*)valor, sdslen(valor),                     ZIPLIST_TAIL);         }         o->ptr = zl;






        /* Comprobar si la lista zip debe convertirse en una tabla hash */
        if (hashTypeLength(o) > server.hash_max_ziplist_entries)
            hashTypeConvert(o, OBJ_ENCODING_HT);
    } else if (o->codificación == OBJ_ENCODING_HT) {         dictEntry *de = dictFind(o->ptr,field);         si (de) {             sdsfree(dictGetVal(de));             if (flags & HASH_SET_TAKE_VALUE) {                 dictGetVal(de) = valor;                 valor = NULO;             } else {                 dictGetVal(de) = sdsdup(valor);             }             actualizar = 1;         } más {             sds f,v;












            if (banderas & HASH_SET_TAKE_FIELD) {                 f = campo;                 campo = NULO;             } más {                 f = sdsdup(campo);             }             if (flags & HASH_SET_TAKE_VALUE) {                 v = valor;                 valor = NULO;             } más {                 v = sdsdup(valor);             }             dictAdd(o->ptr,f,v);         }     } else {         serverPanic("Codificación hash desconocida");     }















    /* Cadenas SDS gratuitas a las que no hicimos referencia en ningún otro lugar si las banderas
     * quieren que esta función sea responsable. */
    if (flags & HASH_SET_TAKE_FIELD && field) sdsfree(field);
    if (flags & HASH_SET_TAKE_VALUE && value) sdsfree(value);
    actualización de retorno;
}

Por lo tanto, hay dos situaciones que se ejecutarán, una es verificar si la longitud de la cadena de valores es mayor a 64, y la otra es verificar si el tamaño de las entradas contenidas es mayor a 512, es decir, comparar con los dos valores de server.hash_max_ziplist_value y server.hash_max_ziplist_entries, estos dos valores se pueden configurar en redis.conf:

hash-max-ziplist-entries 512
hash-max-ziplist-value 64

Como se muestra arriba, ziplist tiene tres estructuras para la implementación de hashTable, en dict.h:

 

dictEntrada

 

typedef struct dictEntry {     void *key;         union {        //Debido a que hay múltiples tipos de valor, value usa union para almacenar         void *val;         uint64_t u64;         int64_t s64;         double d;     } v;     struct dictEntry *next;   next node La dirección se usa para manejar colisiones, y los elementos asignados al mismo índice forman una lista enlazada.¿Recuerdas que también hay una entrada en HashMap, el mismo concepto} dictEntry;








dictado

 

La implementación de una tabla hash usará un depósito para almacenar la dirección de dictEntry . Generalmente, el valor obtenido por hash(key)%len es el índice de depósitos. Este valor determina en qué índice de depósitos queremos colocar este nodo dictEntry. Esto Los cubos son en realidad lo que llamamos tablas hash. La tabla en la estructura dictt de dict.h almacena las direcciones de los cubos

 

/* Esta es nuestra estructura de tabla hash . Cada diccionario tiene dos  de esto a medida que     *        
 implementamos una repetición incremental, para la tabla antigua a la nueva. */ typedef struct
dictht {     dictEntry **table           ; siempre se mantiene como 2^n     máscara de tamaño largo sin firmar,     máscara, utilizada para calcular el índice de cubos correspondiente al valor hash     sin firmar usado durante mucho tiempo,         cuántos nodos dictEntry tiene el dicttht actual } dicttht;




dictar

 

Dictht es el núcleo de hash, pero solo un dicttht no es suficiente, como repetición, hash transversal y otras operaciones, por lo que redis define un dict para admitir varias operaciones del diccionario. Cuando dicttht necesita expandirse/reducirse, se usa para administrar dicttht Migration , el código fuente es el siguiente:

 

typedef struct dict {     dictType *type;         dictType almacena un montón de punteros de función de funciones de utilidad     void *privdata;         guarda datos     dictht ht[2]             que algunas funciones en tipo necesitan como parámetros ; dos dictht, ht[0] Usualmente usado, ht[ 1] se usa para la repetición (por ejemplo, se requiere repetición cuando se expande la capacidad)     long rehashidx; /* la repetición no está en curso si rehashidx == -1 */ ¿ Qué índice de la repetición actual a los cubos, -1 significa estado de no repetición     iteradores largos sin firmar; /* número de iteradores que se están ejecutando actualmente */ número de iteradores seguros. } dictado;







Por ejemplo, si queremos almacenar un dato en una tabla hash, primero calcularemos el código hash correspondiente a la clave a través de un murmullo, y luego obtendremos la ubicación del cubo de acuerdo con el módulo del código hash, y luego lo insertaremos. en la lista enlazada. bosquejo del mapa:

Resumen de uso:

127.0.0.1:6379> hset tiene valor tfield

(entero) 1

127.0.0.1:6379> hset tiene tfield1 value1

(entero) 1

127.0.0.1:6379> hgetall tiene

1) "campo"

2) "valor"

3) "campo1"

4) "valor1"

127.0.0.1:6379> hget tiene tfield

"valor"

127.0.0.1:6379> hdel tiene tfield

(entero) 1

127.0.0.1:6379> hgetall tiene

1) "campo1"

2) "valor1"

 

4. Tipo de colección

 

Conjunto de colección, desordenado y no repetitivo. Las operaciones comunes de los tipos de colección son agregar o eliminar elementos de la colección y determinar si existe un elemento. Dado que el tipo de colección es una tabla hash vacía (tabla hash) utilizada dentro de redis, la complejidad temporal de estas operaciones es O(1).

La estructura de datos subyacente de Set se almacena en intset o hashtable. Cuando el conjunto contiene solo elementos enteros, intset se usa para el almacenamiento; de lo contrario, la tabla hash se usa para el almacenamiento, pero para el conjunto, el valor de la tabla hash se usa para ser NULL. Almacenar elementos por clave. El gráfico es similar al hash, excepto que el valor en la entrada usa el campo.

 

Resumen de uso:

127.0.0.1:6379> triste bb

(entero) 1

127.0.0.1:6379> triste bc

(entero) 1

127.0.0.1:6379> triste cc

(entero) 1

127.0.0.1:6379> MIEMBROS b

1) "c"

2) "b"

127.0.0.1:6379> SISMEMBER bc

(entero) 1

127.0.0.1:6379> SREM antes de Cristo

(entero) 1

127.0.0.1:6379> MIEMBROS b

1) "b"

 

5. Recogida ordenada

Un conjunto ordenado en realidad asocia una puntuación con cada elemento, y las puntuaciones pueden ser las mismas. La codificación de un objeto de colección ordenado puede ser ziplist o skiplist. Use la codificación ziplist cuando se cumplan las siguientes condiciones al mismo tiempo:

El número de elementos es inferior a 128.

La longitud de todos los miembros es inferior a 64 bytes.

El límite superior de las dos condiciones anteriores se puede modificar mediante zset-max-ziplist-entries y zset-max-ziplist-value. La colección ordenada codificada en ziplist se almacena utilizando nodos de lista comprimida que están uno al lado del otro, el primer nodo contiene el miembro y el segundo contiene la puntuación. Los elementos de la colección en la lista zip están ordenados por puntaje de menor a mayor, y aquellos con puntajes más bajos se enumeran en el encabezado.

 

La capa inferior del conjunto ordenado codificado en la lista de saltos es una estructura llamada zset, y una estructura zset contiene tanto un diccionario como una lista de saltos. La tabla de saltos guarda todos los elementos de la colección según la puntuación de menor a mayor . El diccionario guarda la asignación del miembro al puntaje, de modo que el valor del puntaje correspondiente al miembro se pueda encontrar con una complejidad O(1). Aunque las dos estructuras se usan al mismo tiempo, compartirán el miembro y la puntuación del mismo elemento a través de punteros, por lo que no se desperdiciará memoria adicional.

 

Entonces, ¿qué es una lista de salteados?

Skip List (lista de saltos) es una estructura de datos aleatoria basada en una lista enlazada paralela, que es simple de implementar y la complejidad de inserción, eliminación y búsqueda es O (logN). En pocas palabras, la lista de saltos también es una especie de lista enlazada , pero agrega una función de salto sobre la base de la lista enlazada. Es esta función de salto la que permite que la lista de saltos proporcione una complejidad de tiempo O(logN) al buscar elementos .

 

En una lista enlazada de este tipo, si queremos encontrar ciertos datos, debemos comparar uno por uno desde el principio hasta que encontremos el nodo que contiene los datos, o encontrar el primer nodo más grande que los datos dados (no encontrado). Es decir, la complejidad del tiempo es O(n). Del mismo modo, cuando queremos insertar nuevos datos, tenemos que pasar por el mismo proceso de búsqueda para determinar la posición de inserción.

Si agregamos un puntero por cada dos nodos adyacentes , dejemos que el puntero apunte al siguiente nodo, como se muestra en la siguiente figura:

 

De esta forma, todos los punteros recién agregados se conectan a una nueva lista enlazada, pero el número de nodos que contiene es solo la mitad del número original (7, 19, 26 en la figura anterior). Ahora, cuando queremos encontrar datos, primero podemos buscar a lo largo de esta nueva lista enlazada. Cuando encuentre un nodo más grande que los datos a verificar, regrese a la lista enlazada original para buscar. Por ejemplo, si queremos buscar 23, la ruta de búsqueda es a lo largo de la dirección señalada por el puntero rojo en la figura a continuación:

    • Primero se compara 23 con 7, luego se compara con 19, más grande que todos ellos, y se sigue comparando hacia atrás.
    • Pero cuando se compara 23 con 26, es más pequeño que 26, así que vuelva a la lista enlazada a continuación (la lista enlazada original) y compárela con 22.
    • 23 es mayor que 22, siga el puntero a continuación para comparar con 26. 23 es menor que 26, lo que indica que el dato a verificar 23 no existe en la lista enlazada original, y su posición de inserción debe estar entre 22 y 26.

 

En este proceso de búsqueda, debido al puntero recién agregado, ya no necesitamos comparar uno por uno con cada nodo en la lista vinculada. El número de nodos que deben compararse es solo aproximadamente la mitad del original.

 

Usando el mismo método, podemos continuar agregando un puntero por cada dos nodos adyacentes en la lista vinculada recién generada en la capa superior, generando así una lista vinculada de tercera capa. Como se muestra abajo:

En esta nueva estructura de lista enlazada de tres capas, si todavía buscamos 23, entonces lo primero que debemos comparar a lo largo de la lista enlazada superior es 19, y encontramos que 23 es más grande que 19, y luego sabemos que solo tenemos que ir a la parte posterior de 19 para continuar con la búsqueda. Por lo tanto, todos los nodos delante de 19 se saltan a la vez. Es concebible que cuando la lista enlazada es lo suficientemente larga, este método de búsqueda de lista enlazada de múltiples capas nos permite omitir muchos nodos de nivel inferior, lo que acelera enormemente la búsqueda.

 

Skiplist se inspiró en la idea de esta lista enlazada de varias capas. De hecho, según el método de generación de listas enlazadas anterior, el número de nodos en cada capa de la capa superior es la mitad del número de nodos en la capa inferior, por lo que el proceso de búsqueda es muy similar a una búsqueda binaria, por lo que la complejidad temporal de la búsqueda se puede reducir a O(log n) . Sin embargo, este método tiene un gran problema a la hora de insertar datos . Después de insertar un nuevo nodo, se interrumpirá la estricta correspondencia 2:1 entre el número de nodos en las listas enlazadas adyacentes superior e inferior. Si desea mantener esta correspondencia, debe reajustar todos los nodos detrás del nodo recién insertado (incluido el nodo recién insertado), lo que reducirá la complejidad del tiempo a O(n). Eliminar datos tiene el mismo problema.

 

Para evitar este problema, skiplist no requiere una correspondencia estricta entre el número de nodos entre las listas enlazadas adyacentes superior e inferior, sino que selecciona aleatoriamente un nivel para cada nodo . Por ejemplo, si el número de capas obtenidas aleatoriamente por un nodo es 3, entonces se vincula a la lista vinculada de tres capas desde la capa 1 a la capa 3. Para expresarlo claramente, la siguiente figura muestra cómo formar una lista de salteados a través de una operación de inserción paso a paso:

 

Del proceso de creación e inserción de la lista anterior, se puede ver que el nivel de cada nodo se selecciona aleatoriamente , y una nueva inserción de un nodo no afectará los niveles de otros nodos. Por lo tanto, la operación de inserción solo necesita modificar los punteros antes y después del nodo insertado y no necesita ajustar muchos nodos . Esto reduce la complejidad de la operación de inserción. De hecho, esta es una característica muy importante de skiplist, que la hace significativamente mejor que la solución de árbol balanceado en el rendimiento de inserción . Esto se mencionará más adelante.

 

Skiplist se refiere a que, además de la lista enlazada 1 inferior, generará varias capas de listas enlazadas dispersas , y los punteros en estas listas enlazadas omiten deliberadamente algunos nodos (y cuanto más alta es la lista enlazada, más nodos se omiten). Esto nos permite buscar en la lista vinculada de alto nivel primero cuando buscamos datos, luego bajarla capa por capa y finalmente bajar a la lista vinculada de primer nivel para determinar con precisión la ubicación de los datos . En este proceso, saltamos algunos nodos, lo que también acelera la búsqueda.

 

La lista de saltos que acabamos de crear contiene un total de 4 capas de listas enlazadas. Ahora supongamos que todavía buscamos 23. La siguiente figura muestra la ruta de búsqueda:

Cabe señalar que el proceso de inserción de cada nodo que se muestra arriba en realidad pasa por un proceso de búsqueda similar antes de la inserción, y la operación de inserción se completa después de que se determina la posición de inserción.

Cada nodo de la lista de saltos en aplicaciones prácticas debe contener dos partes, clave y valor . En la descripción anterior, no distinguimos específicamente entre clave y valor, pero de hecho, la lista se ordena según la clave (puntuación), y el proceso de búsqueda también se compara según la clave.

 

El proceso de calcular números aleatorios cuando se realizan operaciones de inserción es un proceso crítico, que tiene un impacto significativo en las propiedades estadísticas de skiplist. Este no es un número aleatorio ordinario sujeto a distribución uniforme , su proceso de cálculo es el siguiente:

  • En primer lugar, cada nodo debe tener un puntero de capa 1 (cada nodo está en la lista enlazada de capa 1).
  • Si un nodo tiene un puntero a la capa i (i>=1) (es decir, el nodo ya está en la lista enlazada de la capa 1 a la capa i), entonces la probabilidad de que tenga un puntero a la capa (i+1) es pág.
  • No se permite que el número máximo de capas de un nodo exceda un valor máximo, registrado como MaxLevel.

 

Comparación de skiplist con árbol balanceado y tabla hash

 

Los elementos de skiplist y varios árboles equilibrados (como AVL, árbol rojo-negro, etc.) están ordenados, mientras que la tabla hash no está ordenada. Por lo tanto, solo se puede realizar una búsqueda de clave única en la tabla hash y no es adecuada para la búsqueda de rango. La llamada búsqueda de rango se refiere a encontrar todos los nodos cuyo tamaño está entre dos valores especificados.

 

Los árboles equilibrados son más complejos que las operaciones de lista salteada cuando se realizan búsquedas de rango. En el árbol equilibrado, después de encontrar un valor pequeño en el rango especificado, debemos continuar buscando otros nodos que no excedan el valor grande en el orden del recorrido en orden. El recorrido en orden aquí no es fácil de implementar sin algunas modificaciones al árbol balanceado. Es muy simple realizar una búsqueda de rango en la lista de saltos. Solo necesita recorrer la lista enlazada de la primera capa durante varios pasos después de encontrar el valor pequeño.

Las operaciones de inserción y eliminación del árbol equilibrado pueden provocar el ajuste del subárbol, que es complejo en lógica, mientras que la inserción y eliminación de la lista de saltos solo necesita modificar los punteros de los nodos adyacentes, lo cual es simple y rápido.

En términos de uso de memoria, skiplist es más flexible que el árbol balanceado. En términos generales, cada nodo del árbol balanceado contiene 2 punteros (que apuntan a los subárboles izquierdo y derecho respectivamente), y el número de punteros contenidos en cada nodo de la lista salteada es 1/(1-p) en promedio, dependiendo del tamaño del parámetro p. Si se toma p=1/4 como la implementación en Redis, entonces cada nodo contiene 1,33 punteros en promedio, lo que es más ventajoso que el árbol equilibrado.

La complejidad temporal de encontrar una clave única, una lista de saltos y un árbol equilibrado es O(log n), que es aproximadamente la misma; mientras que la tabla hash mantiene una probabilidad de colisión de valor hash baja, la complejidad del tiempo de búsqueda es cercana a O(1), con mayor rendimiento. Por lo tanto, las diversas estructuras de mapas o diccionarios que solemos usar se implementan principalmente en base a tablas hash.

 

En comparación con la dificultad de la implementación de algoritmos, skiplist es mucho más simple que el árbol balanceado.

Resumen de uso:

127.0.0.1:6379> zadd zset-key 728 miembros1

(entero) 1

127.0.0.1:6379> zadd zset-key 733 miembros2

(entero) 1

127.0.0.1:6379> zadd zset-key 299 miembros3

(entero) 1

127.0.0.1:6379> ZRANGE zset-key 0 -1

1) "miembros3"

2) "miembros1"

3) "miembros2"

127.0.0.1:6379> ZRANGEBYSCORE zset-clave 0 500

1) "miembros3"

127.0.0.1:6379> ZREM zset-key miembros3

(entero) 1

 

 

Referencia: https://www.jianshu.com/p/cc379427ef9d

Referencia: https://blog.csdn.net/qq_35433716/article/details/82177585

 

Referencia: "Combate Redis"

 

 

 

 

 

Supongo que te gusta

Origin blog.csdn.net/qq_22059611/article/details/104099590
Recomendado
Clasificación