Según mi entendimiento, pienso:
- Es perfectamente legal que dos objetos tengan el mismo código hash.
- Si dos objetos son iguales (usando el método equals ()), entonces tienen el mismo código hash.
- 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: HashMap
internamente usa el código hash del objeto. Entonces, si dos objetos pueden tener el mismo código hash, ¿cómo puede HashMap
rastrear qué clave utiliza?
¿Alguien puede explicar cómo HashMap
usa internamente el código hash del objeto?
java
hashmap
hashcode
hash-function
akshay
fuente
fuente
Respuestas:
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()
yequals()
claves:Si dos claves son iguales (
equals()
devuelvetrue
cuando las compara), suhashCode()
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.fuente
hashCode()
método devuelve códigos hash diferentes, entonces los métodosequals()
yhashCode()
de la clase de clave violan el contrato y obtendrá resultados extraños al usar esas claves en aHashMap
.HashMap
, que puede encontrar en el archivosrc.zip
en su directorio de instalación de JDK.Su tercera afirmación es incorrecta.
Es perfectamente legal que dos objetos desiguales tengan el mismo código hash. Se utiliza
HashMap
como 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).
fuente
HashMap
es una matriz deEntry
objetosConsidere
HashMap
como solo un conjunto de objetos.Echa un vistazo a lo que esto
Object
es :Cada
Entry
objeto representa un par clave-valor. El campo senext
refiere a otroEntry
objeto si un depósito tiene más de unoEntry
.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
next
campo y así sucesivamente. La última entrada se refiere anull
.Cuando crea un
HashMap
con el constructor predeterminadoLa matriz se crea con el tamaño 16 y el equilibrio de carga predeterminado de 0.75.
Agregar un nuevo par clave-valor
hash % (arrayLength-1)
donde se debe colocar el elemento (número de cubo)HashMap
, el valor se sobrescribe.Si el cubo ya tiene al menos un elemento, se agrega uno nuevo y se coloca en la primera posición del cubo. Sus
next
campo se refiere al elemento antiguo.Supresión
hash % (arrayLength-1)
Entry
. Si no se encuentra un elemento deseado, regresenull
fuente
hash % (arrayLength-1)
, seríahash % arrayLength
. Pero en realidad lo eshash & (arrayLength-1)
. Es decir, porque usa potencias de dos (2^n
) para la longitud de la matriz, tomandon
bits menos significativos.int
que, por supuesto, puede ser negativo, hacer un módulo en un número negativo te dará un número negativoPuede 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.
fuente
Aquí hay una descripción aproximada del
HashMap
mecanismo de, para laJava 8
versión, (puede ser ligeramente diferente de Java 6) .Estructuras de datos
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.Cuando el recuento de elementos en un depósito es pequeño, se utiliza una lista vinculada individualmente.
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:
Porque tiene una propiedad hash.
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:
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
threshold
yloadFactor
, por lo tanto, no es necesario definirlo como un campo de clase.Podría obtener la capacidad efectiva a través de:
capacity()
Operaciones
Primero encuentre el depósito por valor hash, luego haga un bucle en la lista vinculada o busque el árbol ordenado.
Primero encuentre el cubo según el valor hash de la clave.
Luego intente encontrar el valor:
Cuando se
threshold
alcanza, 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
complejidad del tiempo es
O(1)
porque:O(1)
.O(1)
.O(1)
noO(log N)
.fuente
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.
fuente
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.
fuente
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.
fuente
Actualización de Java 8 en HashMap-
haces esta operación en tu código -
entonces, suponga que su código hash regresó para ambas claves
"old"
y"very-old"
es el mismo. Entonces que pasará.myHashMap
es 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ónentonces
"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)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
ahora cuando ejecutas la siguiente declaración:
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.
fuente
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.
(Fuente)
fuente
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.
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
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
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:
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.
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.
fuente
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:
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 -
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?
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
hasHCode
las claves“misused”
y“horsemints”
utiliza las fórmulas descritas anteriormente, verá que ambas nos dan lo mismo1069518484
. Whooaa !! lección aprendida -Ahora el mapa hash se ve así:
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í.
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 :)
fuente
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:
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,
equals
la tecla se usa para encontrar el elemento real en la lista vinculada.Veamos el
put()
método: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) & hash
aquíi
está el índice donde se colocará el nuevo elemento (o es el "cubo").n
es el tamaño de latab
matriz (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.
fuente