¿Cómo un Java HashMap maneja diferentes objetos con el mismo código hash?

223

Según mi entendimiento, pienso:

  1. Es perfectamente legal que dos objetos tengan el mismo código hash.
  2. Si dos objetos son iguales (usando el método equals ()), entonces tienen el mismo código hash.
  3. Si dos objetos no son iguales, entonces no pueden tener el mismo código hash

¿Estoy en lo correcto?

Ahora, si estoy en lo correcto, tengo la siguiente pregunta: HashMapinternamente usa el código hash del objeto. Entonces, si dos objetos pueden tener el mismo código hash, ¿cómo puede HashMaprastrear qué clave utiliza?

¿Alguien puede explicar cómo HashMapusa internamente el código hash del objeto?

akshay
fuente
29
Para el registro: # 1 y # 2 son correctos, # 3 está mal: dos objetos que no son iguales pueden tener el mismo código hash.
Joachim Sauer
66
# 1 y # 3 son contradictorios incluso
Delfic
De hecho, si no se sigue el # 2, entonces la implementación equals () (o posiblemente el hashCode ()) es incorrecta.
Joachim Sauer

Respuestas:

346

Un hashmap funciona así (esto está un poco simplificado, pero ilustra el mecanismo básico):

Tiene una cantidad de "cubos" que utiliza para almacenar pares clave-valor. Cada segmento tiene un número único: eso es lo que identifica el segmento. Cuando coloca un par clave-valor en el mapa, el hashmap mirará el código hash de la clave y almacenará el par en el cubo cuyo identificador es el código hash de la clave. Por ejemplo: el código hash de la clave es 235 -> el par se almacena en el número de depósito 235. (Tenga en cuenta que un depósito puede almacenar más de un par clave-valor).

Cuando busca un valor en el hashmap, al darle una clave, primero verá el código hash de la clave que proporcionó. El hashmap luego buscará en el depósito correspondiente y luego comparará la clave que proporcionó con las claves de todos los pares en el depósito, comparándolas con equals().

Ahora puede ver cómo esto es muy eficiente para buscar pares clave-valor en un mapa: mediante el código hash de la clave, el mapa hash sabe de inmediato en qué cubo buscar, de modo que solo tiene que probar lo que hay en ese cubo.

Mirando el mecanismo anterior, también puede ver qué requisitos son necesarios en los métodos hashCode()y equals()claves:

  • Si dos claves son iguales ( equals()devuelve truecuando las compara), su hashCode()método debe devolver el mismo número. Si las claves violan esto, entonces las claves que son iguales podrían almacenarse en diferentes segmentos, y el hashmap no podría encontrar pares clave-valor (porque se verá en el mismo segmento).

  • Si dos claves son diferentes, no importa si sus códigos hash son iguales o no. Se almacenarán en el mismo depósito si sus códigos hash son los mismos, y en este caso, el hashmap se usará equals()para distinguirlos.

Jesper
fuente
44
usted escribió "y el hashmap no podría encontrar pares clave-valor (porque se verá en el mismo grupo)". ¿Puede explicar que se verá en el mismo segmento? Diga que esos dos objetos iguales son t1 y t2 y son iguales y t1 y t2 tienen los códigos hash h1 y h2 respectivamente. ¡Entonces t1.equals (t2) = verdadero y h1! = H2 Entonces, cuando el hashmap buscaría t1, ¿buscará en el depósito h1 y para t2 en el depósito t2?
Geek
19
Si dos claves son iguales pero su hashCode()método devuelve códigos hash diferentes, entonces los métodos equals()y hashCode()de la clase de clave violan el contrato y obtendrá resultados extraños al usar esas claves en a HashMap.
Jesper
Cada depósito puede tener múltiples pares de valores clave, que son usos de la lista vinculada internamente. Pero mi confusión es: ¿qué es el cubo aquí? ¿Qué estructura de datos utiliza internamente? ¿Hay alguna conexión entre los cubos?
Ankit Sharma
1
@AnkitSharma Si realmente desea conocer todos los detalles, busque el código fuente de HashMap, que puede encontrar en el archivo src.zipen su directorio de instalación de JDK.
Jesper
1
@ 1290 La única relación entre las claves en el mismo depósito es que tienen el mismo código hash.
Jesper
88

Su tercera afirmación es incorrecta.

Es perfectamente legal que dos objetos desiguales tengan el mismo código hash. Se utiliza HashMapcomo un "filtro de primer paso" para que el mapa pueda encontrar rápidamente posibles entradas con la clave especificada. Las claves con el mismo código hash se prueban para la igualdad con la clave especificada.

No querrá un requisito de que dos objetos desiguales no puedan tener el mismo código hash, ya que de lo contrario eso lo limitaría a 2 32 posibles objetos. (También significaría que diferentes tipos ni siquiera podrían usar los campos de un objeto para generar códigos hash, ya que otras clases podrían generar el mismo hash).

Jon Skeet
fuente
66
¿Cómo llegaste a 2 ^ 32 objetos posibles?
Geek
55
Llego tarde, pero para aquellos que aún se preguntan: un código hash en Java es un int, y un int tiene 2 ^ 32 valores posibles
Xerus
69

Diagrama de estructura de HashMap

HashMapes una matriz de Entryobjetos

Considere HashMapcomo solo un conjunto de objetos.

Echa un vistazo a lo que esto Object es :

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;
 
}

Cada Entry objeto representa un par clave-valor. El campo se nextrefiere a otro Entryobjeto si un depósito tiene más de uno Entry.

A veces puede suceder que los códigos hash para 2 objetos diferentes sean iguales. En este caso, dos objetos se guardarán en un depósito y se presentarán como una lista vinculada. El punto de entrada es el objeto agregado más recientemente. Este objeto se refiere a otro objeto con el nextcampo y así sucesivamente. La última entrada se refiere a null.

Cuando crea un HashMapcon el constructor predeterminado

HashMap hashMap = new HashMap();

La matriz se crea con el tamaño 16 y el equilibrio de carga predeterminado de 0.75.

Agregar un nuevo par clave-valor

  1. Calcular hashcode para la clave
  2. Calcular posición hash % (arrayLength-1) donde se debe colocar el elemento (número de cubo)
  3. Si intenta agregar un valor con una clave que ya se ha guardado en HashMap , el valor se sobrescribe.
  4. De lo contrario, el elemento se agrega al depósito.

Si el cubo ya tiene al menos un elemento, se agrega uno nuevo y se coloca en la primera posición del cubo. Susnext campo se refiere al elemento antiguo.

Supresión

  1. Calcular el código hash para la clave dada
  2. Calcular el número de cubeta hash % (arrayLength-1)
  3. Obtenga una referencia al primer objeto Entrada en el depósito y, mediante el método igual, itere sobre todas las entradas en el depósito dado. Finalmente encontraremos el correcto Entry. Si no se encuentra un elemento deseado, regresenull
Sergii Shevchyk
fuente
3
Esto está mal hash % (arrayLength-1), sería hash % arrayLength. Pero en realidad lo es hash & (arrayLength-1) . Es decir, porque usa potencias de dos ( 2^n) para la longitud de la matriz, tomando nbits menos significativos.
weston
Creo que no es una matriz de objetos Entity, sino una matriz de LinkedList / Tree. Y cada árbol tiene internamente objetos de entidad.
Mudit bhaintwal
@shevchyk ¿por qué almacenamos claves y hash? para que sirven ¿No estamos desperdiciando memoria aquí?
roottraveller
hashset usa internamente hashmap. Qué reglas de adición y eliminación de hashmap, son válidas para hashset?
intercambio excesivo el
2
@weston no solo eso, el hashCode es algo intque, por supuesto, puede ser negativo, hacer un módulo en un número negativo te dará un número negativo
Eugene
35

Puede encontrar información excelente en http://javarevisited.blogspot.com/2011/02/how-hashmap-works-in-java.html

Para resumir:

HashMap funciona según el principio de hashing

put (clave, valor): HashMap almacena la clave y el objeto de valor como Map.Entry. Hashmap aplica el código hash (clave) para obtener el depósito. si hay colisión, HashMap usa LinkedList para almacenar objetos.

get (key): HashMap usa el código hash de Key Object para encontrar la ubicación del depósito y luego llama al método keys.equals () para identificar el nodo correcto en LinkedList y devolver el objeto de valor asociado para esa clave en Java HashMap.

Abhijit Gaikwad
fuente
3
Encontré la respuesta proporcionada por Jasper mejor, sentí que el blog está más orientado a manejar la entrevista, que a comprender el concepto
Narendra N
@NarendraN Estoy de acuerdo contigo.
Abhijit Gaikwad
22

Aquí hay una descripción aproximada del HashMapmecanismo de, para la Java 8versión, (puede ser ligeramente diferente de Java 6) .


Estructuras de datos

  • Tabla de
    hash El valor de hash se calcula mediante la hash()tecla on, y decide qué segmento de la tabla hash usar para una clave determinada.
  • Lista vinculada (individualmente)
    Cuando el recuento de elementos en un depósito es pequeño, se utiliza una lista vinculada individualmente.
  • Árbol rojo-negro
    Cuando el recuento de elementos en un cubo es grande, se utiliza un árbol rojo-negro.

Clases (internas)

  • Map.Entry
    Representar una sola entidad en el mapa, la entidad clave / valor.
  • HashMap.Node
    Versión de lista vinculada del nodo.

    Podría representar:

    • Un cubo de hachís.
      Porque tiene una propiedad hash.
    • Un nodo en una lista vinculada individualmente (por lo tanto, también encabeza la lista vinculada ) .
  • HashMap.TreeNode
    Versión en árbol del nodo.

Campos (internos)

  • Node[] table
    La tabla de cubo, (encabezado de las listas vinculadas).
    Si un cubo no contiene elementos, entonces es nulo, por lo tanto, solo ocupa espacio de una referencia.
  • Set<Map.Entry> entrySet Conjunto de entidades.
  • int size
    Número de entidades.
  • float loadFactor
    Indique qué tan llena está permitida la tabla hash, antes de cambiar el tamaño.
  • int threshold
    El siguiente tamaño para cambiar el tamaño.
    Fórmula:threshold = capacity * loadFactor

Métodos (internos)

  • int hash(key)
    Calcular hash por clave.
  • ¿Cómo mapear hash al cubo?
    Use la siguiente lógica:

    static int hashToBucket(int tableSize, int hash) {
        return (tableSize - 1) & hash;
    }

Sobre la capacidad

En la tabla hash, la capacidad significa el recuento de cubetas, se podría obtener table.length.
También podría calcularse mediante thresholdy loadFactor, por lo tanto, no es necesario definirlo como un campo de clase.

Podría obtener la capacidad efectiva a través de: capacity()


Operaciones

  • Encontrar entidad por clave.
    Primero encuentre el depósito por valor hash, luego haga un bucle en la lista vinculada o busque el árbol ordenado.
  • Añadir entidad con clave.
    Primero encuentre el cubo según el valor hash de la clave.
    Luego intente encontrar el valor:
    • Si se encuentra, reemplace el valor.
    • De lo contrario, agregue un nuevo nodo al comienzo de la lista vinculada o insértelo en un árbol ordenado.
  • Cambiar tamaño
    Cuando se thresholdalcanza, duplicará la capacidad de la tabla hash ( table.length), luego realizará un nuevo hash en todos los elementos para reconstruir la tabla.
    Esta podría ser una operación costosa.

Actuación

  • get & put La
    complejidad del tiempo es O(1)porque:
    • Se accede al cubo a través del índice de matriz, por lo tanto O(1).
    • La lista vinculada en cada cubo es de pequeña longitud, por lo que podría verse como O(1) .
    • El tamaño del árbol también es limitado, ya que extenderá la capacidad y el nuevo hash cuando aumente el recuento de elementos, por lo que podría verlo como O(1)no O(log N).
Eric Wang
fuente
¿Puede dar un ejemplo? ¿Cómo tiene la complejidad del tiempo? O (1)
Jitendra
@jsroyal Esto podría explicar la complejidad más claramente: en.wikipedia.org/wiki/Hash_table . En resumen: encontrar el segmento de destino es O (1), porque lo encuentra por índice en una matriz; luego, dentro de un depósito, la cantidad de elementos es pequeña y, en promedio, un número constante a pesar del número total de elementos en toda la tabla hash, por lo que buscar el elemento objetivo dentro de un depósito también es O (1); así, O (1) + O (1) = O (1).
Eric Wang
14

El código hash determina qué depósito debe verificar el mapa hash. Si hay más de un objeto en el depósito, se realiza una búsqueda lineal para encontrar qué elemento del depósito es igual al equals()método deseado (utilizando el método).

En otras palabras, si tiene un código hash perfecto, entonces el acceso al mapa hash es constante, nunca tendrá que recorrer un bucket (técnicamente, también tendría que tener MAX_INT buckets, la implementación de Java puede compartir algunos códigos hash en el mismo bucket para reducir los requisitos de espacio). Si tiene el peor código hash (siempre devuelve el mismo número), entonces su acceso al mapa hash se vuelve lineal ya que debe buscar a través de cada elemento en el mapa (todos están en el mismo depósito) para obtener lo que desea.

La mayoría de las veces un código hash bien escrito no es perfecto, pero es lo suficientemente único como para brindarle un acceso más o menos constante.

Paso
fuente
11

Estás equivocado en el punto tres. Dos entradas pueden tener el mismo código hash pero no ser iguales. Eche un vistazo a la implementación de HashMap.get desde OpenJdk . Puede ver que comprueba que los hashes son iguales y las claves son iguales. Si el punto tres fuera verdadero, entonces sería innecesario verificar que las claves sean iguales. El código hash se compara antes que la clave porque el primero es una comparación más eficiente.

Si está interesado en aprender un poco más sobre esto, eche un vistazo al artículo de Wikipedia sobre resolución de colisiones de direccionamiento abierto , que creo que es el mecanismo que utiliza la implementación de OpenJdk. Ese mecanismo es sutilmente diferente del enfoque de "cubo" que menciona una de las otras respuestas.

Leif Wickland
fuente
6
import java.util.HashMap;

public class Students  {
    String name;
    int age;

    Students(String name, int age ){
        this.name = name;
        this.age=age;
    }

    @Override
    public int hashCode() {
        System.out.println("__hash__");
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        System.out.println("__eq__");
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Students other = (Students) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

    public static void main(String[] args) {

        Students S1 = new Students("taj",22);
        Students S2 = new Students("taj",21);

        System.out.println(S1.hashCode());
        System.out.println(S2.hashCode());

        HashMap<Students,String > HM = new HashMap<Students,String > (); 
        HM.put(S1, "tajinder");
        HM.put(S2, "tajinder");
        System.out.println(HM.size());
    }
}

Output:

__ hash __

116232

__ hash __

116201

__ hash __

__ hash __

2

Entonces, aquí vemos que si los objetos S1 y S2 tienen contenido diferente, entonces estamos bastante seguros de que nuestro método de Hashcode anulado generará un Hashcode diferente (116232,11601) para ambos objetos. AHORA ya que hay diferentes códigos hash, por lo que ni siquiera se molestará en llamar al método EQUALS. Porque un Hashcode diferente GARANTIZA DIFERENTE contenido en un objeto.

    public static void main(String[] args) {

        Students S1 = new Students("taj",21);
        Students S2 = new Students("taj",21);

        System.out.println(S1.hashCode());
        System.out.println(S2.hashCode());

        HashMap<Students,String > HM = new HashMap<Students,String > (); 
        HM.put(S1, "tajinder");
        HM.put(S2, "tajinder");
        System.out.println(HM.size());
    }
}

Now lets change out main method a little bit. Output after this change is 

__ hash __

116201

__ hash __

116201

__ hash __

__ hash __

__ eq __

1
We can clearly see that equal method is called. Here is print statement __eq__, since we have same hashcode, then content of objects MAY or MAY not be similar. So program internally  calls Equal method to verify this. 


Conclusion 
If hashcode is different , equal method will not get called. 
if hashcode is same, equal method will get called.

Thanks , hope it helps. 
Tajinder Singh
fuente
3

dos objetos son iguales, implica que tienen el mismo código hash, pero no al revés.

2 objetos iguales ------> tienen el mismo código hash

2 objetos tienen el mismo código hash ---- xxxxx -> NO son iguales

Actualización de Java 8 en HashMap-

haces esta operación en tu código -

myHashmap.put("old","old-value");
myHashMap.put("very-old","very-old-value");

entonces, suponga que su código hash regresó para ambas claves "old"y "very-old"es el mismo. Entonces que pasará.

myHashMapes un HashMap y supongamos que inicialmente no especificó su capacidad. Entonces, la capacidad predeterminada según java es 16. Entonces, tan pronto como haya inicializado el hashmap con la nueva palabra clave, creó 16 cubos. ahora cuando ejecutaste la primera declaración

myHashmap.put("old","old-value");

entonces "old"se calcula el código hash para , y debido a que el código hash también podría ser un entero muy grande, entonces Java internamente hizo esto - (hash es el código hash aquí y >>> es el desplazamiento a la derecha)

hash XOR hash >>> 16

para dar una imagen más grande, devolverá algún índice, que estaría entre 0 y 15. Ahora su par de valores clave "old"y"old-value" se convertiría en la variable de instancia de valor y clave del objeto Entry. y luego este objeto de entrada se almacenará en el depósito, o puede decir que en un índice particular, este objeto de entrada se almacenaría.

FYI- Entry es una clase en Map interface- Map.Entry, con estas firmas / definiciones

class Entry{
          final Key k;
          value v;
          final int hash;
          Entry next;
}

ahora cuando ejecutas la siguiente declaración:

myHashmap.put("very-old","very-old-value");

y "very-old"proporciona el mismo código hash que "old", por lo que este nuevo par de valores clave se envía nuevamente al mismo índice o al mismo depósito. Pero como este cubo no está vacío, entonces elnext variable del objeto Entrada se usa para almacenar este nuevo par de valores clave.

y esto se almacenará como una lista vinculada para cada objeto que tenga el mismo código hash, pero se especifica un TRIEFY_THRESHOLD con el valor 6. así que después de que esto llegue, la lista vinculada se convierte al árbol equilibrado (árbol rojo-negro) con el primer elemento como raíz.

anubhs
fuente
respuesta increíble (y)
Sudhanshu Gaur
2

Cada objeto de entrada representa un par clave-valor. El siguiente campo se refiere a otro objeto Entrada si un depósito tiene más de 1 Entrada.

A veces puede suceder que los códigos hash para 2 objetos diferentes sean iguales. En este caso, 2 objetos se guardarán en un depósito y se presentarán como LinkedList. El punto de entrada es el objeto agregado más recientemente. Este objeto se refiere a otro objeto con el siguiente campo y uno. La última entrada se refiere a nulo. Cuando creas HashMap con el constructor predeterminado

La matriz se crea con el tamaño 16 y el equilibrio de carga predeterminado de 0.75.

ingrese la descripción de la imagen aquí

(Fuente)

Premraj
fuente
1

El mapa hash funciona según el principio de hash

El método get (Key k) de HashMap llama al método hashCode en el objeto clave y aplica hashValue devuelto a su propia función hash estática para encontrar una ubicación de depósito (matriz de respaldo) donde las claves y los valores se almacenan en forma de una clase anidada llamada Entry (Map). Entrada) . Entonces, ha concluido que de la línea anterior, tanto la clave como el valor se almacenan en el depósito como una forma de objeto Entrada. Por lo tanto, pensar que solo se almacena el valor en el cubo no es correcto y no dará una buena impresión al entrevistador.

  • Siempre que llamemos al método get (Key k) en el objeto HashMap. Primero verifica si la clave es nula o no. Tenga en cuenta que solo puede haber una clave nula en HashMap.

Si la clave es nula, las claves nulas siempre se asignan al hash 0, por lo tanto, el índice 0.

Si la clave no es nula, llamará a la función hash en el objeto clave, consulte la línea 4 en el método anterior, es decir, key.hashCode (), por lo que después de que key.hashCode () devuelve hashValue, la línea 4 se ve como

            int hash = hash(hashValue)

y ahora, aplica el valor hash devuelto en su propia función hash.

Podríamos preguntarnos por qué estamos calculando el valor hash nuevamente usando hash (hashValue). La respuesta es Defiende contra funciones hash de baja calidad.

Ahora, el valor hash final se usa para encontrar la ubicación del depósito en la que se almacena el objeto Entrada. El objeto de entrada se almacena en el depósito de esta manera (hash, clave, valor, índice de depósito)


fuente
1

No entraré en detalles sobre cómo funciona HashMap, pero daré un ejemplo para que podamos recordar cómo funciona HashMap relacionándolo con la realidad.

Tenemos Key, Value, HashCode y bucket.

Por algún tiempo, relacionaremos cada uno de ellos con lo siguiente:

  • Cubo -> Una sociedad
  • HashCode -> Dirección de la sociedad (única siempre)
  • Valor -> Una casa en la sociedad
  • Clave -> Dirección de la casa.

Usando Map.get (clave):

Stevie quiere llegar a la casa de su amigo (Josse) que vive en una villa en una sociedad VIP, que sea JavaLovers Society. La dirección de Josse es su número de seguro social (que es diferente para todos). Hay un índice mantenido en el que descubrimos el nombre de la Sociedad basado en el SSN. Este índice puede considerarse un algoritmo para descubrir el HashCode.

  • Nombre de la Sociedad SSN
  • 92313 (Josse's) - JavaLovers
  • 13214 - AngularJSLovers
  • 98080 - JavaLovers
  • 53808 - Amantes de la biología

  1. Este SSN (clave) primero nos da un HashCode (de la tabla de índice) que no es más que el nombre de la Sociedad.
  2. Ahora, las casas múltiples pueden estar en la misma sociedad, por lo que el HashCode puede ser común.
  3. Supongamos que la Sociedad es común para dos casas, ¿cómo vamos a identificar a qué casa vamos? Sí, usando la tecla (SSN) que no es más que la dirección de la Casa

Usando Map.put (clave, valor)

Esto encuentra una sociedad adecuada para este Valor al encontrar el HashCode y luego se almacena el valor.

Espero que esto ayude y que esté abierto a modificaciones.

Prashant K
fuente
0

Será una respuesta larga, tomar un trago y seguir leyendo ...

El hash consiste en almacenar un par clave-valor en la memoria que se puede leer y escribir más rápido. Almacena claves en una matriz y valores en LinkedList.

Digamos que quiero almacenar 4 pares de valores clave:

{
girl => ahhan , 
misused => Manmohan Singh , 
horsemints => guess what”, 
no => way
}

Entonces, para almacenar las claves necesitamos una matriz de 4 elementos. Ahora, ¿cómo asigno una de estas 4 claves a 4 índices de matriz (0,1,2,3)?

Entonces, Java encuentra el código hash de claves individuales y las asigna a un índice de matriz particular. Hashcode Formulas es -

1) reverse the string.

2) keep on multiplying ascii of each character with increasing power of 31 . then add the components .

3) So hashCode() of girl would be –(ascii values of  l,r,i,g are 108, 114, 105 and 103) . 

e.g. girl =  108 * 31^0  + 114 * 31^1  + 105 * 31^2 + 103 * 31^3  = 3173020

Hash y niña !! Sé lo que estás pensando. Tu fascinación por ese dúo salvaje podría hacerte perder algo importante.

¿Por qué Java lo multiplica por 31?

Es porque 31 es un primo impar en la forma 2 ^ 5 - 1. Y la prima impar reduce la posibilidad de colisión de hash

Ahora, ¿cómo se asigna este código hash a un índice de matriz?

respuesta es, Hash Code % (Array length -1) . Entonces “girl”se asigna a(3173020 % 3) = 1 en nuestro caso. que es el segundo elemento de la matriz.

y el valor "ahhan" se almacena en una LinkedList asociada con el índice de matriz 1.

HashCollision : si intenta encontrar hasHCodelas claves “misused”y “horsemints”utiliza las fórmulas descritas anteriormente, verá que ambas nos dan lo mismo 1069518484. Whooaa !! lección aprendida -

2 objetos iguales deben tener el mismo código hash pero no hay garantía de que el código hash coincida, entonces los objetos son iguales. Por lo tanto, debe almacenar los valores correspondientes a "mal uso" y "horsemints" en el depósito 1 (1069518484% 3).

Ahora el mapa hash se ve así:

Array Index 0 
Array Index 1 - LinkedIst (“ahhan , Manmohan Singh , guess what”)
Array Index 2  LinkedList (“way”)
Array Index 3  

Ahora, si algún cuerpo intenta encontrar el valor de la clave “horsemints”, Java encontrará rápidamente el código hash de la misma, lo modulará y comenzará a buscar su valor en la lista correspondiente.index 1 . De esta manera, no necesitamos buscar en los 4 índices de la matriz, lo que hace que el acceso a los datos sea más rápido.

Pero espera, un segundo. hay 3 valores en esa lista enlazada correspondiente al índice de matriz 1, ¿cómo descubre cuál era el valor para las "mentas" clave?

En realidad mentí, cuando dije que HashMap solo almacena valores en LinkedList.

Almacena ambos pares de valores clave como entrada de mapa. Entonces, en realidad, Map se ve así.

Array Index 0 
Array Index 1 - LinkedIst (<”girl => ahhan”> , <” misused => Manmohan Singh”> , <”horsemints => guess what”>)
Array Index 2  LinkedList (<”no => way”>)
Array Index 3  

Ahora puede ver que mientras recorre la lista enlazada correspondiente a ArrayIndex1, en realidad compara la clave de cada entrada de esa lista enlazada con "horsemints" y cuando encuentra una, simplemente devuelve el valor de la misma.

Espero que te hayas divertido mientras lo leías :)

sapy
fuente
Creo que esto está mal: "Almacena las claves en una matriz y los valores en una LinkedList".
ACV
Cada elemento de la lista para cada depósito contiene la clave y el valor, así como la referencia al siguiente nodo.
ACV
0

Como se dice, una imagen vale más que 1000 palabras. Yo digo: algo de código es mejor que 1000 palabras. Aquí está el código fuente de HashMap. Obtener método:

/**
     * Implements Map.get and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @return the node, or null if none
     */
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

Entonces queda claro que el hash se usa para encontrar el "depósito" y el primer elemento siempre se verifica en ese depósito. De lo contrario, equalsla tecla se usa para encontrar el elemento real en la lista vinculada.

Veamos el put()método:

  /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

Es un poco más complicado, pero queda claro que el nuevo elemento se coloca en la pestaña en la posición calculada en función del hash:

i = (n - 1) & hashaquí iestá el índice donde se colocará el nuevo elemento (o es el "cubo"). nes el tamaño de la tabmatriz (matriz de "cubos").

Primero, se intenta ponerlo como el primer elemento de ese "cubo". Si ya hay un elemento, agregue un nuevo nodo a la lista.

ACV
fuente