Colecciones Marco Serie mapa (once): LinkeHashMap

directorio

1 Información general

principio 2

3 de análisis de código fuente

 3.0 Sistema de Entrada herencia

 3.1 el proceso de establecer la lista de

 3.2 eliminación lista de nodos proceso

 Para los procedimientos de mantenimiento de acceso 3.3

 3.4 Basado en LinkedHashMap implementar el almacenamiento en caché

4 Resumen

 

 

 

 

 

1. Visión general

LinkedHashMap heredado de HashMap, HashMap en base, mediante el mantenimiento de una lista doblemente enlazada, no podemos resolver el HashMap de estancia en orden de recorrido y problemas de orden de inserción consistentes. Además, para LinkedHashMap también proporciona acceso al soporte relacionado. En algunos escenarios, esta característica es útil, tal como el almacenamiento en caché. En la realización, LinkedHashMap muchas maneras, directa heredaron de HashMap, sólo para mantener una lista doblemente enlazada parte de sobreescritura del método. Por lo tanto, para entender el código fuente LinkedHashMap, es necesario entender fuente HashMap. Acerca de análisis de código fuente HashMap, este documento no tiene la intención de empezar a hablar para arriba. Se puede hacer referencia a mi artículo anterior " HashMap detallado análisis de código fuente (JDK1.8) ." En ese artículo, yo estaba con una docena o un gráfico para ayudarle a aprender fuente HashMap.

La estructura de este artículo y mis dos artículos anteriores sobre las clases de colección de Java ( el marco de las colecciones ) de análisis de código fuente de los diferentes artículos, este artículo no se analizará el funcionamiento básico de clases de colección (búsqueda, travesía, insertar, eliminar), pero en lugar de centrarse en mantenimiento de la lista doblemente enlazada. Incluyendo el proceso de establecer la lista, eliminar un nodo de proceso, tales como el acceso y procesar la orden de mantenimiento. Bueno, tomará comenzó el análisis.

 2. Principio

LinkedHashMap heredado del capítulo anterior, dijo que el HashMap, por lo que todavía está basado en la estructura zip-hash subyacente. La estructura consiste en una matriz o vinculado lista que consiste en un árbol rojo-negro, una vista esquemática de la estructura de la siguiente forma:

LinkedHashMap sobre la base de la estructura anterior, un aumento de una lista doblemente enlazada, de manera que la estructura anterior se puede mantener en el orden de introducción de llave. Simultáneamente por la operación de la lista enlazada correspondiente, el orden de acceso para lograr lógica asociada. La estructura puede ser como se muestra a continuación:

La figura anterior, la flecha de color azul claro indica un precursor a una referencia, la flecha roja indica que las referencias posteriores. Cada vez que se inserta una nueva clave de nodo, el nuevo nodo, finalmente, la cola de contacto posteriores referencias al nodo. La cola se moverá al nuevo nodo de referencia, tal lista doblemente enlazada está configurado.

La estructura anterior no es difícil de entender, a pesar de la introducción de árbol rojo-negro, haciendo un poco más compleja algunas miradas de la estructura. Pero podemos ignorar el árbol rojo-negro, y sólo se ocupa de la estructura de la lista en sí. Bueno, a continuación, entrar en el análisis de detalle de la misma.

 3. análisis de código fuente

 3.0 Sistema de Entrada herencia

Antes del inicio del análisis del núcleo, en el que el primer corte en jerarquía de herencia para analizar nodos clave. Echemos un vistazo a la herencia diagrama de la arquitectura:

El sistema de herencia anterior es todavía un poco complicado a primera vista, sino también un poco confuso. Hashmap NodoArbol clase interna no se hereda una clase interna de nodo, pero subclases entrada LinkedHashMap clase interna de la auto-nodo. Aquí hay alguna razón para hacerlo, y no voy a hablar aquí. En primer lugar para explicar brevemente por encima de la jerarquía de herencia. hereda de Entrada LinkedHashMap clase interna de la clase HashMap interna de nodo, y dos nuevas referencias, respectivamente antes y después. Ambos citados el uso de difícil de entender, que se utiliza para mantener una lista doblemente enlazada. Mientras tanto, la clase TreeNode hereda interna Entrada LinkedHashMap, que tienen la capacidad de entrada y otra lista enlazada juntos. Pero aquí tenemos que considerar un problema. Cuando usamos HashMap, NodoArbol no necesita tener la capacidad de hacer la lista. Si hereda LinkedHashMap clase interna de entrada, TreeNode en más de dos referencias utilizadas, esto no es un desperdicio de espacio? Explica brevemente el problema (es limitada, y no garantiza totalmente correcta), realmente sería un desperdicio de espacio para hacerlo, pero con la capacidad de lista enlazada NodoArbol hereda la obtenida a través de estos residuos es la pena. HashMap ideas de diseño en los comentarios, tiene esto que decir:

Debido TreeNodes son aproximadamente dos veces el tamaño de los nodos regulares, que 
los utilizan solamente cuando los contenedores contienen suficientes nodos a uso orden 
(véase TREEIFY_THRESHOLD). Y cuando se vuelven demasiado pequeña (debido a la eliminación o cambio de tamaño) que están de vuelta convertido a contenedores de civil. En 
usos con hashcodes usuario bien distribuidos, son contenedores de árboles 
rara vez se utilizan.

Generalmente significa que el tamaño del objeto TreeNode es alrededor de 2 veces los objetos nodo normal, que sólo utilizan suficientemente comprende de nuevo un nodo en la bañera (bin) en el. Cuando el número de nodos en la cubeta se vuelve baja (en función de la expansión y de eliminación), TreeNode se convierte en nodo. Cuando una distribución buen método hashCode implementado por el usuario, el tipo de árbol, rara vez se utiliza la bañera.

A través de los comentarios anteriores, podemos entender. En circunstancias normales, siempre y cuando la aplicación hashCode no es malo, la composición de la lista de nodos rara vez se convierta en un árbol rojo-negro está compuesto por un TreeNode. Que el uso NodoArbol no hay mucho espacio emaciación punto que es aceptable. Si los hereda de la clase mecanismo NodoArbol de nodo, entonces es con el fin de tener la capacidad de hacer la lista, tenemos que hereda la clase interna LinkedHashMap nodo de entrada (no quiere decir que la herencia no se puede lograr, sino más bien de la estructura del código, el fin de convertirse en lista de artículos de LinkedHashMap heredó clase, por lo que la estructura lógica más claramente, porque esta clase es existir lista enlazada). Esta vez más mal que bien, perder una gran cantidad de espacio para adquirir la capacidad de utilizar no necesariamente conseguir.

Una vez dicho esto, debemos ser capaces de entender el tipo de jerarquía de herencia de nodos. Mirar hacia fuera para aquí solo, preparando el terreno para el siguiente análisis. Ligeramente narrativa de largo aliento, perdóname.

 3.1 el proceso de establecer la lista de

El proceso de establecer una lista de pares de valores clave insertada nodo inicial de la situación inicial, dejó LinkedHashMap la cabeza y la cola referencias que apunte al nuevo nodo al mismo tiempo, incluso si se establece la lista. Luego están siempre se inserta un nuevo nodo a través de un nuevo punto de referencia hacia atrás nodo de acceso de nodo en la cola, se puede lograr la lista de actualizaciones.

tipos de mapas clase de colección de pares de valores clave se insertan a través método put (K, V), un LinkedHashMap sí puso no anular el método de la clase padre, pero utiliza la realización de la clase padre. Sin embargo, en el HashMap, método PUT de la inserción de un nodo interno Tipo de nodo clase HashMap, el tipo de nodo no tiene la capacidad LinkedHashMap sus subtipos clase interna de Entrada nodos lista enlazada. Por lo tanto, la lista LinkedHashMap es cómo construirla? Antes del comienzo de la explicación, vamos a ver LinkedHashMap código de inserción operaciones relacionadas:

// HashMap implementado en 
PUT V pública (clave K, el valor V) { 
    retorno PutVal (el hash (Key), clave, el valor false, true); 
} 

// HashMap lograr la 
última V putVal (int hachís, K clave, V valor, onlyIfAbsent Boolean, 
               Boolean el evict) { 
    el nodo <K, V> [] Tab; el nodo <K, V> P; n-int, I; 
    IF ((Tab = Tabla) == null || (= n-tab.length ) == 0) {...} 
    // hash de la posición del cucharón por el nodo de posicionamiento se encuentra, y detecta si el cubo contiene el nodo referenciado 
    if ((p = ficha [i = (n - 1) y de hash]) == null) {...} 
    else { 
        nodo <K, V> E; K K; 
        SI (p.hash el hash == && 
            ((K = p.key) == || clave (key = null && clave!. los iguales (K)))) 
            E = P; 
        el else if (el instanceof la NodoArbol P) {...} 
        el else { 
            // recorrer la lista, y los recuentos de la longitud de la lista
            for (int BinCount = 0 ;; BinCount ++) { 
                // no se encuentra nodo para ser insertado en una única lista enlazada, el nuevo nodo conectado detrás de una sola lista 
                IF ((E = p.next) == null) { 
                    P = la .Next newNode (el hash, Key, el valor, null); 
                    IF (. BinCount> = TREEIFY_THRESHOLD - 1) {...} 
                    PAUSA; 
                } 
                // insertado nodo ya presentes en una única lista enlazada 
                si (e.hash == de hash && 
                    ((K = e.key) == || clave (key = null && key.equals (K))!)) 
                    PAUSA; 
                P = E; 
            } 
        } 
        if (E = null) {// para la asignación existente clave 
            V = oldValue e.Value;
            SI (onlyIfAbsent oldValue == null ||!) {...} 
            afterNodeAccess (E); // método de devolución de llamada, describió posterior 
            retorno oldValue; 
        } 
    } 
    ++ ModCount; 
    SI (tamaño ++> umbral) {...} 
    afterNodeInsertion (evict); // método de devolución de llamada, describió subsiguiente 
    nulo de retorno; 
} 

// implementado en el HashMap 
el nodo <K, V> el newNode (int el hash, Key K, el valor de V, el nodo <K, V> Siguiente) { 
    return new nuevo el nodo <> (el hash, Key, el valor, Siguiente); 
} 

// sobrescribir un LinkedHashMap 
el nodo <K, V> el newNode (int el hash, Key K, el valor de V, el nodo <K, V> E) { 
    LinkedHashMap.Entry <K , V> P = 
        nuevo nuevo LinkedHashMap.Entry <K, V> (el hash, Key, valor, E); 
    // entrada conectada a la cola de una lista doblemente enlazada 
    linkNodeLast (P); 
    retorno P; 
} 

un LinkedHashMap // implementado en 
privada linkNodeLast void (LinkedHashMap.Entry <K, V> P) { 
    LinkedHashMap.Entry <K, V> Última = cola; 
    cola = P; 
    // última es nulo, aún lista Mostrar el establecimiento de 
    IF (Última == null) 
        cabeza = P; 
    el else { 
        nuevo nodo se conecta a la p // lista cola 
        p.before = Última; 
        last.after = P; 
    } 
}

Lo anterior se inserta en el LinkedHashMap fuente pertinente omitido aquí algún código no crítica. Me acuerdo con el código anterior, puede llamar a una operación de inserción LinkedHashMap procedimiento conocido. De la siguiente manera:

método que newNode marcado un fondo rojo, este paso es más crítica. LinkedHashMap reemplazar este método. En este método, creado LinkedHashMap entrada, el método de entrada por linkNodeLast conectado a la cola de una lista doblemente enlazada a lograr el establecimiento de una lista doblemente enlazada. Una vez establecida una lista doblemente enlazada, podemos seguir orden de inserción a LinkedHashMap travesía, podemos escribir su propio código de prueba a fin de inserción punto de prueba.

Estos se insertan en la secuencia de mantenimiento análisis de correlación LinkedHashMap. Por último, en esta sección, a continuación, añadir algunas cosas adicionales. Si miramos con atención en el código anterior, se dará cuenta de que hay dos en afterel inicio del método, no mencionados anteriormente. En JDK fuente 1,8 HashMap, los tres métodos asociados:

// devoluciones de llamada para permitir LinkedHashMap post-acciones 
void afterNodeAccess (Nodo <K, V> p) {} 
void afterNodeInsertion (evict boolean) {} 
void afterNodeRemoval (Nodo <K, V> p) {}

De acuerdo con los comentarios de estos tres métodos se pueden ver, el uso de estos métodos es que después de las adiciones y supresiones de investigación y otras operaciones, a modo de corrección, deja LinkedHashMap la oportunidad de hacer un poco después de la operación. método detallado implementado en los tres LinkedHashMap se ha descrito anteriormente, la primera sección no lograr tales análisis, el análisis de correlación se llevará a cabo en las secciones subsiguientes.

 3.2 eliminación lista de nodos proceso

A medida que la operación de inserción, LinkedHashMap borrar el código en cuestión se logra directamente por la clase padre. Cuando se elimina un nodo, elimine la clase padre lógica no repara una lista doblemente enlazada LinkedHashMap mantiene, que no es su responsabilidad. Y a continuación, los nodos más adelante suprime, los nodos se eliminan de la lista doblemente enlazada cómo quitarlo? Por supuesto, todavía hay un camino. En un método de devolución de llamada mencionada en último lugar de tres para el funcionamiento de un HashMap LinkedHashMap para algunas operaciones para responder. Así, después de eliminar y el nodo, el método de devolución de llamada  afterNodeRemoval se invoca. LinkedHashMap reemplazar este método, y completar la operación de eliminación del nodo que desea eliminar en el proceso. El código fuente relevante de la siguiente manera:

// HashMap中实现
V remove pública (clave Object) { 
    Nodo <K, V> e; 
    retorno (e = removeNode (almohadilla (clave), llave, nula, falsa, verdadera)) == null? 
        null: e.value; 
} 

// HashMap中实现
Nodo final <K, V> removeNode (int de hash, clave de objetos, del valor del objeto, 
                           boolean matchValue, móvil boolean) { 
    ficha Nodo <K, V> []; Nodo <K, V> p; int n, índice; 
    if ((tab = tabla) = null && (n = tab.length)> 0 &&! 
        (p = pestaña! [índice = (n - 1) y de hash]) = null) { 
        Nodo <K, V> nodo = null, e; K k; V v; 
        si (== p.hash de hash && 
            ((k = p.key) || clave ==! = (tecla nula &&.
            SI (el instanceof la NodoArbol P) {...} 
            el {else 
                // lista única iterate, buscando el nodo que desea eliminar, y asignado al nodo variables 
                do { 
                    SI (e.hash el hash == && 
                        ((K = e.key) = || Key = 
                         )) (= null && key.equals Key (K)!) { 
                        Node = E; 
                        PAUSA; 
                    } 
                    P = E; 
                !} el tiempo ((E = e.next) = null); 
            } 
        } 
        if (! = null && nodo (matchValue || (V = node.value!) == valor ||
                             (Valor! = Null && value.equals (V)))) { 
            IF (la instanceof el Nodo TreeNode) {...} 
            // nodo a borrar se retira de una sola lista enlazada 
        (LinkedHashMap.Entry <K, V>) E, B = p.before, A = p.after ;
            SI el otro (Nodo == P) 
                Tab [índice] = node.next; 
            el otro 
                p.next = node.next; 
            ++ ModCount; 
            --size; 
            afterNodeRemoval (Node); // llamada al método de devolución de llamada de borrado operaciones posteriores 
            de regreso nodo ; 
        } 
    } 
    retorno nulo; 
} 

// sobrescribir un LinkedHashMap 
void afterNodeRemoval (el nodo <, V K> E) {// el unlink 
    LinkedHashMap.Entry <, V K> p = 
    nodo sucesor referencia prodrómica // p-blanking 
    = null = p.after p.before; 
    // B es nulo, muestra que p es el nodo cabeza 
    IF (== null B) 
        cabeza = A; 
    el otro 
        b.Después = A; 
    // A es nulo, el nodo de cola se indica que p
    si (a nulo ==) 
        de cola = b; 
    más 
        A.Before = b; 
}

proceso de borrado no es complicado, el código anterior es en realidad mucho que hacer tres cosas:

  1. El cubo hash para localizar la posición
  2. método Delete de atravesar la lista, o llamar relacionados árbol rojo-negro
  3. Para borrar un nodo se elimina de la lista doblemente enlazada mantenido por LinkedHashMap

Como un ejemplo para explicar que si queremos eliminar el diagrama inferior es un nodo clave 3.

La dispersión del nodo destino pertenece a la cuba 3, y en la preservación de la bañera 3 para un solo recorrido de lista enlazada. Después de encontrar el nodo que desea eliminar, a partir de la eliminación de un único nodo en la lista. De la siguiente manera:

A continuación, retire el nodo y lista entonces doblemente enlazada:

proceso de borrado y relacionado reparación no es complicado, en conjunción con la imagen de arriba, debemos ser muy fácil de entender, no hay mucho que decir.

 Para los procedimientos de mantenimiento de acceso 3.3

Hemos dicho orden de inserción realización anterior, esta sección decir algo sobre el orden de acceso. Por defecto, LinkedHashMap es mantener el orden de inserción. Pero podemos inicializar LinkedHashMap, accessOrder parámetro designado es cierto, se puede acceder a él a fin de mantener la lista. En principio, el acceso para que no se complica, cuando llamamos get/getOrDefault/replacea otros métodos, estos métodos simplemente quieren nodo de acceso se mueve al final de la lista. Correspondiente código fuente es el siguiente:

// LinkedHashMap sobrescribir 
GET V pública (clave de objeto) { 
    Nodo <K, V> E; 
    SI ((E = el getNode (el hash (Key), Key)) == null) 
        return null; 
    // es cierto si accessOrder , entonces la llamada será afterNodeAccess nodo de acceso se mueve a la última lista de 
    SI (accessOrder) 
        afterNodeAccess (e); 
    retorno e.Value; 
} 

// en una anulación LinkedHashMap 
vacío afterNodeAccess (nodo <K, V > e) {// nodo para mover Última 
    LinkedHashMap.Entry <K, V> Última; 
    if (! accessOrder && (Última = cola) = E) { 
        LinkedHashMap.Entry <K, V> P = 
            (LinkedHashMap.Entry <K, V>) E, B = P .before, a = p.after; 
        p.after = null; 
        // si b es nulo, mostró por primera p nodo  
        si (b == null)
            cabeza = a;
        la otra cosa 
            b.Después = A; 
            
        si (a! = null) 
            A.Before = B; 
        / * 
         * Aquí duda, la condición de la rama padre no ha asegurar que el nodo es el nodo final e, 
         * no necesariamente e.after es nulo, rama else no sabe cuál es el papel 
         * / 
        persona 
            última = B; 
    
        SI (última == null) 
            la cabeza = p; 
        else { 
            // Contacto P con el último de la lista 
            p.before = último; 
            last.after = p; 
        } 
        cola P =; 
        ++ ModCount; 
    } 
}

Arriba está el orden de acceso de código de aplicación, no es complicado. Los siguientes ejemplos muestran que, ayuda a entender. Supongamos que visita siguiente tabla es el nodo clave 3, visite la antigua estructura:

Después de la visita, la clave para el nodo 3 se moverá a la última posición doblemente lista de su predecesor y sucesor seguirá la actualización vinculado. Después de la visita se estructura de la siguiente manera:

 3.4 Basado en LinkedHashMap implementar el almacenamiento en caché

Visto cómo la inserción LinkedHashMap y mantenimiento del orden de acceso, que deben tener una cierta comprensión de los principios de LinkedHashMap. En esta sección ponemos en práctica lo que escribir algo de código, aquí implementa una simple estrategia de caché LRU través LinkedHashMap herencia. Antes de escribir el código, introducir primero un conocimiento previo.

Mientras que la Sección 3.1 analiza el proceso de establecer la lista, ignoré deliberadamente algunos de los análisis de código fuente. En esta sección se puso arriba en la parte ignorada, vistazo al código fuente es:

afterNodeInsertion void (Boolean El evict) {// Eliminar Posiblemente mayor 
    LinkedHashMap.Entry <K, V> Primera; 
    // nodo determina si se debe quitar la menos recientemente visitada de acuerdo con las condiciones 
    (evict && (primera = cabeza) si = null && removeEldestEntry! (Primera)) { 
        K = first.key Key; 
        la removeNode (el hash (Key), Key, null, false, true); 
    } 
} 

// eliminar la menos recientemente visitada una de las condiciones, se pueden lograr diferentes estrategias cubriendo este método caché 
protegido Boolean la removeEldestEntry (de Map.Entry <K, V> mayor) { 
    retorno a false; 
}

La fuente anterior de lógica de la base no se ejecutará en circunstancias normales, no había analizado previamente. El código anterior es relativamente sencillo de hacer, es decir, a través de una serie de condiciones para determinar si se debe quitar el nodo menos recientemente visitada. Vemos aquí, hay que conocer el propósito de los dos métodos anteriores. Cuando nos damos cuenta LinkedHashMap caché de base, sobreescribiendo removeEldestEntrypuede implementar un método de caché LRU política personalizada. Por ejemplo, podemos determinar si se debe quitar el nodo menos recientemente visitada, o para determinar si se debe quitar el nodo basado en el tiempo de supervivencia en función del número de nodos y otros nodos. Esta sección se consigue mediante el almacenamiento en caché el número de nodos para determinar si las políticas basadas rebasamiento. Cuando se construye el objeto de caché, pasando el número máximo de nodos. Al insertar el número de nodos supera el número máximo de nodos, los nodos quitan menos recientemente. Los códigos son los siguientes:

public class SimpleCache <K, V> extiende LinkedHashMap <K, V> { 

    static final privado int MAX_NODE_NUM = 100; 

    int limite privado; 

    SimpleCache pública () { 
        esto (MAX_NODE_NUM); 
    } 

    Pública SimpleCache (límite int) { 
        super (límite, 0.75f, true); 
        this.limit = límite; 
    } 

    V pública Guardar (tecla K, V val) { 
        put retorno (clave, val); 
    } 

    V getOne pública (clave K) { 
        get antirretorno (referencia); 
    } 

    Existe booleano pública (clave K) { 
        volver containsKey (clave); 
    } 
    
    / ** 
     *判断节点数是否超限
     * @param mayor 
     * @return超限返回cierto,否则返回falsa
     * / 
    @ Override 
    protegida removeEldestEntry booleano (Map.Entry <K, V> mayor) { 
        tamaño de retorno ()> límite; 
    } 
}

Código de ensayo es el siguiente:

{Clase SimpleCacheTest pública 

    @test 
    prueba public void () throws Exception { 
        SimpleCache <Integer, Integer> Caché nueva nueva SimpleCache = <> (3.); 

        For (int i = 0; I <10; i ++) { 
            cache.save (I, ; I * I) 
        } 

        "llave 10 se inserta en los pares, el contenido de la cache:" System.out.println (); 
        System.out.println (cache + "\ n-"); 

        System.out.println "clave de acceso ( después de que el valor del nodo 7, el contenido de la cache: "); 
        cache.getOne (7); 
        System.out.println (cache +" \ n- "); 

        System.out.println (" llave insertada en el par clave-valor 1 después el contenido de la cache: "); 
        . cache.save (1 ,. 1); 
        System.out.println (cache); 
    } 
}

resultados de la prueba son los siguientes:

151670457400271

En el código de prueba, el tamaño de caché se establece en 3. Después de insertar el par clave-valor 10 a la caché, sólo los últimos tres se conservan, otros han sido eliminados. nodo de acceso y luego la tecla 7 para que, finalmente, la posición del nodo se mueve a una lista doblemente enlazada. Cuando vuelva a insertar un par clave-valor como un nodo clave 7 no será eliminado.

Como complemento a esta sección en la parte delantera, una breve introducción a la LinkedHashMap aplicación en otras áreas. Esta sección y el código en cuestión no es difícil de entender, no aquí en la repetición.

 4. Resumen

Desde la perspectiva de LinkedHashMap mantener una lista doblemente enlazada de código fuente LinkedHashMap se analiza, y puesto en práctica un simple LinkedHashMap basado Cache al final del artículo. En el desarrollo diario, la frecuencia de uso LinkedHashMap no era tan HashMap, pero también es importante tener en cuenta. En el marco de recopilación de Java, HashMap, LinkedHashMap y TreeMap implementa la clase de mapeo tres funciones diferentes en función de las diferentes estructuras de datos, y. HashMap problema subyacente de hash basado en la estructura de la cremallera, y una cadena larga se introduce en una optimización de árbol rojo-negro en el JDK 1.8. Sobre la base de esta estructura, HashMap proporciona operaciones CRUD eficientes. LinkedHashMap por encima de ella, mediante el mantenimiento de una orden de recorrido de lista doblemente enlazada para lograr la estructura de datos de hash. Basado en el árbol rojo-negro que subyace TreeMap consigue utilizando propiedades árbol rojo-negro para lograr la tecla de función de clasificación.

 

Este enlace: código detallado fuente análisis https://www.tianxiaobo.com/2018/01/24/LinkedHashMap- (JDK1-8) /

 

 

0

Supongo que te gusta

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