Colecciones Marco Serie mapa (doce): TreeMap (1.8)

directorio

Un perfil

dos general

Tres análisis de código fuente

 Encuentra 3.1

 3,2 recorrido

 3.3 inserto

 3.4 Borrar

 

 

 

 

 

 I. Introducción

TreeMapApareció por primera vez en JDK 1.2la aplicación del Marco de colecciones de Java es el más importante. Sobre la base de la TreeMap que subyace 红黑树en práctica, podrá garantizar log(n)containsKey completo consigue, puesto y operaciones de supresión dentro de la complejidad del tiempo, de alta eficiencia. Por otra parte, debido a la TreeMap Based rojo-negro árbol, que es clave para mantener la fundación TreeMap ordenada. En general, el núcleo TreeMap es el árbol rojo-negro, el árbol rojo-negro es también un montón de maneras de comprobar una operación de adiciones y supresiones de bases paquete. Así que, mientras entendemos el árbol rojo-negro, TreeMap nada secreto.

 En segundo lugar, una visión general

TreeMapHereda de AbstractMape implementa  NavigableMapla interfaz. NavigableMap interfaz extiende SortedMapla interfaz, en última instancia, SortedMap heredado de la Mapinterfaz, mientras que la clase AbstractMap también implementa la interfaz del mapa. Estos son los sistema de herencia TreeMap, que describe un caótico de bits, como la figura:

La cifra es la TreeMap diagrama de jerarquía de herencia, más intuitivo. Aquí brevemente sobre la interfaz inusual jerarquía de herencia NavigableMapy SortedMaplas dos interfaces ver nombre conocido significado. Déjame hablar interfaces de NavigableMap, interfaz NavigableMap declara un método serie con capacidades de navegación, tales como:

/ ** 
 * vuelve la más pequeña de tecla correspondiente rojo-negro árbol de la entrada 
 * / 
de Map.Entry <K, V> firstEntry (); 

/ ** 
 * Devuelve el mayor maxKey clave, y sólo menos de maxKey parámetros clave 
 * / 
K lowerKey ( clave K); 

/ ** 
 * vuelve la más pequeña Minkey clave, y mayor que el parámetro única clave Minkey 
 * / 
K higherKey (clave K);

Navegación a través de estos métodos, se puede navegar rápidamente a la tecla o metas de entrada. Como interfaz de SortedMap que proporciona un orden basado en las operaciones clave, tales como

/ ** 
 * devuelve la clave contenidos en [Minkey, TOKEY) Mapa de la gama 
 * / 
a <, V K> headMap, (K TOKEY) SortedMap; (); 

/ ** 
 * devuelve la clave contenida en [fromKey, TOKEY) Rango MAP en 
 * / 
un SortedMap <K, V> el submapa (K a fromKey, TOKEY K);

Estos son la introducción de dos interfaces, muy simples. Como AbstractMap y el mapa no se puede decir aquí, estamos interesados ​​en ver Javadoc por derecho propio. Sobre el sistema de herencia TreeMap aquí cuando se trata de esto, después de entrar en los detalles del análisis.

 En tercer lugar, el análisis de código fuente

JDK 1.8El TreeMapcódigo fuente tiene más de dos mil líneas, o más. Este artículo no tiene la intención de analizar toda la frase de origen por la oración, pero la selección de varios métodos utilizados para el análisis. Estas funciones son el método de búsqueda, transversal, insertar, eliminar, etc. implementadas, otros métodos son poco amigos de análisis propia lata interesados. TreeMapLa parte central se implementa en 红黑树método -implemented de sustancialmente la mayor parte del árbol rojo-negro subyacente es añadido, suprimido, una operación de comprobación del paquete. Sobre uno de ellos dijo, siempre y cuando el árbol rojo-negro de entender el principio, TreeMap nada secreto. En 红黑树principio, por favor refiérase a mi otro artículo - un análisis detallado árbol rojo-negro , este artículo no discutirá esto.

 Encuentra 3.1

TreeMapimplementación árbol rojo-negro, mientras que el árbol rojo-negro es un árbol binario de búsqueda auto-equilibrio, a fin de buscar procedimientos operativos TreeMap y consistente sobre la base de un árbol de búsqueda binaria. Binaria proceso de búsqueda de árbol es así, en primer lugar el valor objetivo y el valor del nodo raíz se comparan, si el valor de destino es menor que el valor del nodo raíz, y luego el hijo izquierdo del nodo raíz para la comparación. Si el valor objetivo es mayor que el valor del nodo raíz, se sigue comparando y hijo derecho del nodo raíz. En el proceso de búsqueda, si un nodo en un árbol binario y el valor objetivo son iguales, devuelve true, false en caso contrario. Encuentra TreeMap y es similar, excepto en el TreeMap, el nodo (Entrada) se almacena en pares de valores clave <k,v>. En el proceso de búsqueda, más es el tamaño de la clave, devuelve el valor de, si no lo encuentra, devuelve null. TreeMap método de búsqueda es get, en la implementación específica getEntrydel proceso, el código fuente correspondiente de la siguiente manera:

V público get (clave Object) { 
    Entrada <K, V> p = getEntry (clave); 
    retorno (p == null: Valor PD); 
} 

La entrada final <, V K> getEntry (Object key) { 
    // Descarga de versión basada en la comparación para bien de rendimiento 
    si (comparador! = Null) 
        volver getEntryUsingComparator (clave); 
    si (clave == null) 
        throw new NullPointerException (); 
    @SuppressWarnings ( "sin control") 
        Comparable <? súper K tecla> k = (Comparable <Super K>?); 
    Entrada <K, V> p = root; 
    
    //查找操作的核心逻辑就在这个mientras循环里
    tiempo (p! = Null) { 
        int cmp = k.compareTo (p.key); 
        si (CMP <0)  
            p = p.left;
        else if (cmp> 0)
            p = p.right; 
        otro 
            de retorno P; 
    } 
    Nula regresar; 
}

Encuentra la operación lógica de la base es el getEntryproceso de whilecirculación, controlamos lo anterior dicho proceso, su propia mirada en ella, es relativamente simple, por no decir.

 3,2 recorrido

Todas las operaciones de recorrido es una mayor frecuencia de operación, para TreeMap, el uso es generalmente como sigue:

(clave del objeto: map.keySet ()) { 
    // hacer algo 
}

o

para (entrada Map.Entry: map.entrySet ()) { 
    // hacer algo 
}

Como puede verse a partir del fragmento de código anterior, que normalmente se establece a una tecla TreeMap o conjunto de Entrada poligonal. El fragmento de código anterior con foreach generada a través del método de conjunto de claves colección, en tiempo de compilación se convierte en un recorrido iterativo, es equivalente a:

Establecer teclas = map.keySet (); 
Iterator ite = keys.iterator (); 
while (ite.hasNext ()) { 
    clave Object = ite.next (); 
    // hacer algo 
}

Por otro lado, el TreeMap tiene una propiedad que puede garantizar la clave ordenada, el valor predeterminado es la secuencia positiva. Así, durante el recorrido, se encuentra TreeMap será de pequeño a grande valor de salida de los bonos. Bueno, entonces tienen que analizar keySetlos métodos, así como cuando se atraviesa método de conjunto de claves conjunto generado, TreeMap es la forma de garantizar claves ordenadas. código relacionado es el siguiente:

Conjunto pública <K> conjunto de claves () { 
    volver navigableKeySet (); 
} 

NavigableSet público <K> navigableKeySet () { 
    KEYSET <K> NKS = navigableKeySet; 
    retorno (NKS! = null)? NKS: (navigableKeySet = nuevo conjunto de claves <> (este)); 
} 

Clase static final de conjunto de claves <E> extiende AbstractSet <E> implementos NavigableSet <E> { 
    final privado NavigableMap <E,?> M; 
    Conjunto de claves (NavigableMap <E,?> Mapa) {m = PAM; } 

    Pública Iterator <E> iterador () { 
        si (m instanceof TreeMap) 
            de retorno ((TreeMap <E,?>) M) .keyIterator (); 
        otro 
            de retorno (<? E,> (TreeMap.NavigableSubMap) m) .keyIterator ();


Iterator <K> keyIterator () { 
    return new KeyIterator (getFirstEntry ()); 
} 

Clase final KeyIterator extiende PrivateEntryIterator <K> { 
    KeyIterator (Entrada <K, V> primero) { 
        super (primera); 
    } 
    K pública siguiente () { 
        llave de volver nextEntry (.); 
    } 
} 

Clase abstracta PrivateEntryIterator <T> implementos Iterator <T> { 
    Entrada <K, V> siguiente; 
    Entrada <K, V> lastReturned; 
    int expectedModCount; 

    PrivateEntryIterator (Entrada <K, V> primero) { 
        expectedModCount = modCount; 
        lastReturned = null; 
        NEXT = primero; 
    } 

        Volver al lado! = Null;

    la Final de entrada <K, V> nextEntry () { 
        la entrada <K, V> E = Siguiente; 
        SI (E == null) 
            de banda nueva NoSuchElementException nuevo (); 
        SI (= ModCount expectedModCount!) 
            al lanzar una ConcurrentModificationException nuevo nuevo (); 
        // Buscar e es el sucesor nodo 
        Siguiente por sucesor = (e); 
        lastReturned = e; 
        retorno e; 
    } 

    // otros métodos omitido 
}

El código anterior más código de conjunto de claves involucrados, o más, podemos mirar desde la parte superior hacia abajo. Como puede verse a partir de los anteriores KEYSET fuente método devuelve el KeySetclase de objeto. Esta clase implementa la Iterableinterfaz, se puede devolver un iterador. La implementación específica es el iterador KeyIterator, y es la clase KeyIterator núcleo lógico PrivateEntryIteratorimplementado. El código anterior son numerosos, pero el código de núcleo o de la clase y de clase de conjunto de claves PrivateEntryIterator  nextEntrymétodos. conjunto de claves de clase es una colección, no se analizan aquí. El método nextEntry es más importante, un simple análisis a continuación.

En KeyIterator inicialización, Entrada TreeMap contiene la clave más pequeña pasó PrivateEntryIterator. Cuando se llama método nextEntry, llamando al método sucesor para encontrar el sucesor de la entrada actual y dejar que el siguiente punto a un sucesor, y finalmente volver a la entrada actual. Devuelve el valor de la clave se pueden realizar en una secuencia lógica positiva de esta manera.

Bueno, TreeMap recorrido operativo hablado de esto. la operación de recorrido en sí no es difícil, pero parecen ser un poco más, un poco largo aliento, tomamos la ofensiva.

 3.3 inserto

Con respecto a las dos primeras operaciones, el inserto es significativamente más complicado. Cuando se coloca en TreeMap nuevos pares de valores clave podrían minar la naturaleza del árbol rojo-negro. Por conveniencia de la descripción del presente documento, la entrada llamada nodos. Y el nodo recién insertado se llama N, N es el nodo padre P. nodo padre de P G, y P es el hijo izquierdo de G. P hermanos es U. Después de la inserción del nuevo nodo N a los árboles de color rojo-negro (rojo nuevo nodo), produce los siguientes cinco casos:

  1. N es el nodo raíz
  2. padre negro nodo N
  3. nodo padre de N es de color rojo, el rojo es también el nodo tío
  4. nodo padre de N es de color rojo, el nodo tío es negro, y N es el hijo derecho de P
  5. nodo padre de N es de color rojo, el nodo tío es negro, y N es el hijo izquierdo de P

5 en el caso anterior, el caso 2 no destruye la naturaleza de color rojo-negro árbol, por lo que no se requiere ninguna acción. 1. Propiedades de árbol rojo-negro destruyen 2 (root negro), donde 3, 4, y 5 destruirá la naturaleza rojo-negro árbol 4 (Red cada nodo tiene dos nodos hijo a ser de color negro). Esta necesidad hora de ajustar para hacer el árbol rojo-negro para recuperar el equilibrio. En cuanto a la forma de ajustar, puedo hacer referencia a otro artículo sobre el árbol rojo-negro ( análisis detallado árbol rojo-negro ), no se repetirá aquí. A continuación, analizar inserción Artículos relacionados:

V PUT pública (clave K, el valor V) { 
    la Entrada <K, V> T = la raíz; 
    // 1. Si el nodo raíz es nulo, un nuevo nodo al nodo raíz 
    IF (T == null) { 
        Comparar (Key, Key); 
        la raíz = new nuevo la entrada <> (clave, valor, null); 
        size = 1;. 
        ModCount ++; 
        return null; 
    } 
    int CMP; 
    la entrada <K, V> padres; 
    // Dividir Comparador y comparable Caminos 
    Comparador <super K? > RCP = Comparador; 
    if (! = RCP null) { 
        // 2. es la clave para encontrar un lugar adecuado en el árbol rojo-negro 
        no { 
            parent = T; 
            CMP = cpr.compare (clave, t.key); 
            SI (CMP < 0) 
                T = t.left;
            SI el otro (CMP> 0) 
                T = t.right; 
            el otro 
                de retorno t.setValue (valor); 
        } el tiempo (T = null!); 
    } La {else 
        lógica // código similar a la anterior se omite 
    } 
    la entrada <K, V> la nueva entrada de nuevo = E <> (Key, el valor, los padres); 
    // 3. el nuevo nodo en una cadena de árbol rojo-negro 
    IF (CMP <0) 
        parent.left = E; 
    el otro 
        parent.right = E; 
    // 4.. insertar un nuevo nodo puede destruir las propiedades del árbol rojo-negro, en la mirada de corrección 
    fixAfterInsertion (E); 
    tamaño ++; 
    ModCount ++; 
    return null; 
}

poner el código método anterior, la lógica árbol binario de búsqueda y el nodo de la lógica de inserción consistente. pasos importantes que he escrito notas, no es difícil de entender. Inserción complejidad lógica en que una operación de reparación después de la inserción, un método correspondiente fixAfterInsertion, y una descripción de la fuente del método son las siguientes:

Aquí, en el inserto terminado. A continuación, dijo TreeMap parte más compleja, que es una operación de eliminación.

 3.4 Borrar

La deleción es la parte más compleja de un árbol rojo-negro, debido a que la operación puede destruir las propiedades de árbol rojo-negro 5 (desde cualquier nodo a todos los caminos simples cada hoja contiene el mismo número de nodos negros), la naturaleza de la reparación a 5 reparación de otras propiedades (propiedades 2 y 4 necesidad de reparación, no fijar la naturaleza. 1 y 3) complejo. Cuando la operación de eliminación condujo a la destrucción de la propiedad 5, 8 sucede. Para la conveniencia de la descripción, aquí o hacer algunas suposiciones. Nosotros 最终被删除reemplazamos el nodo nodo se llama X, X es llamada N. nodo padre de N P, y N es el hijo izquierdo de P. nodo hermano N S, S izquierda niño está SL, el hijo derecho para el SR. Aquí X está especialmente hincapié  最终被删除 nodo, la situación es la razón por un árbol de búsqueda binaria sería que desea borrar un nodo con dos niños en situaciones eliminar sólo un nodo secundario, el nodo es el deseo de ser eliminado predecesor y sucesor del nodo.

A continuación, una simple lista de situaciones que pueden ocurrir cuando a punto de eliminar un nodo, el primero en la lista situación relativamente simple:

  1. Eventualmente borrada nodo nodo X está rojo
  2. el nodo X es negro, pero el nodo hijo del nodo es rojo

situaciones más complejas:

  1. Alternativamente, un nuevo nodo raíz N
  2. Negro N, N hermano nodos S rojo, los otros nodos son de color negro.
  3. Negro N, N nodo padre P, S y S hermano nodos secundarios son de color negro.
  4. N es de color negro, P es rojo, S y S niños son de color negro.
  5. Negro N, P puede ser el negro rojo, S es negro, es S SL roja del hijo izquierdo, SR por el derecho del niño negro
  6. Negro N, P puede ser el negro rojo, S es negro, rojo SR, SL puede ser negro rojo

Ocho situaciones mencionadas anteriormente, los dos primeros proceso relativamente simple, en el caso de la situación después de 6 2,6 complicado. A continuación, se analizará la situación de desplegado 2,6, elimine el código fuente correspondiente de la siguiente manera:

V Quitar pública (clave de objeto) { 
    la entrada <K, V> P = getEntry (Key); 
    SI (P == null) 
        devuelto nulo; 

    V = Valor P. oldValue; 
    deleteEntry (P); 
    retorno oldValue; 
} 

privada deleteEntry vacío (la entrada <K, V> p) { 
    ModCount ++; 
    size--; 

    / * 
     * p 1. Si hay dos nodos hijos, el nodo sucesor se encuentra, 
     * y copia el valor del nodo sucesor del nodo P, y sea p apuntando a su nodo sucesor 
     * / 
    IF (!! = null && p.right p.left = null) { 
        la Entrada <K, V> = S por el sucesor (P); 
        p.key = s.key; 
        Valor PD = S. valor; 
        P = S; 
    } // P 2 tiene hijos 

    // A la sustitución Fixup el nodo de inicio, si existe.
    La entrada <K, V> = repuesto (p.left = null p.left: p.right !?); 
        si (p .color == NEGRO)

    SI (reemplazo! = Null) { 
        / * 
         * 2. Las referencias de los padres de reemplazo apuntan a la nueva matriz, 
         * al tiempo que permite la sustitución señalando nuevo padre. 
         * / 
        Replacement.parent = p.parent; 
        IF (p.parent == null) 
            la raíz = sustitución; 
        el ELSE IF (== p.parent.left P) 
            p.parent.left = sustitución; 
        el otro 
            p.parent.right = reemplazo; 

        // nula son tan fuera vincula permiso para su uso por fixAfterDeletion. 
        p.left = = p.right p.parent = null; 

        // p 3. Si el nodo es el nodo borrado negro, es necesario ajustar 
            fixAfterDeletion (reemplazo ); 
    } else SI (p.parent == null) {// se suprime el nodo raíz, y esto es sólo un nodo en el árbol 
        la raíz = null; 
    } else {// borrar el nodo no es un nodo secundario 
        // p es negro, es necesario ajustar 
        SI (p.color == NEGRO) 
            fixAfterDeletion (P); 

        // P del árbol la eliminación de 
        IF (p.parent = null) { 
            IF (P == p.parent.left) 
                p.parent.left = null; 
            el eLSE IF (P == p.parent.right) 
                p.parent.right = nula; 
            p.parent = null; 
        } 
    } 
}

Como puede verse a partir del código fuente, removeel método no es más que una garantía, en la implementación del núcleo deleteEntryproceso. deleteEntry principalmente a hacer lo que un par de cosas:

  1. Si desea eliminar un nodo P tiene dos hijos, y luego encontrar un sucesor a S P, y luego copiar el valor de S a P, y dejar que el punto P S
  2. Si finalmente eliminada nodo P (P apunta ahora a nodo finalmente suprimido) niño no está vacío, entonces sustituirlo por los nodos secundarios
  3. Si el nodo se elimina finalmente es negro, a continuación, llamar al método para la reparación fixAfterDeletion

Dice que el reemplazo no está vacía, el deleteEntry lógica de ejecución. Dicho lo anterior un poco largo aliento, si nos limitamos a decir, siete palabras para resumir: 找后继 -> 替换 -> 修复. Este tres pasos, la operación más compleja reparación. Para volver a habilitar la operación de reparación de color rojo-negro árbol para restablecer el equilibrio, la fuente de operación de reparación de la siguiente manera:

método fixAfterDeletion como sigue:

FixAfterDeletion cara superior de la lógica del código han sido analizados por forma analítica con el caso de la figura cada procesamiento de la lógica de código. A modo de ilustración, debería ser bastante fácil de entender. Bueno, TreeMap primero analizar el código fuente aquí.

 

 

 

Este enlace:  https://www.tianxiaobo.com/2018/01/11/TreeMap análisis de código fuente /

0

Supongo que te gusta

Origin www.cnblogs.com/youngao/p/12518994.html
Recomendado
Clasificación