Middle
¿Cómo está estructurada y organizada la estructura de datos HashMap en Java?
sobes.tech AI
Respuesta de la IA
HashMap en Java se basa en principios de hash. Almacena pares de "clave-valor".
Estructura interna:
- Array de buckets (contenedores). Cada bucket es una lista enlazada (o un árbol, desde Java 8, cuando hay muchas colisiones).
- Al agregar un elemento (
put):- Se calcula el código hash de la clave (
key.hashCode()). - El código hash se modifica para una mejor distribución (
hash). - Usando el hash modificado y el tamaño del array de buckets, se calcula el índice del bucket donde se colocará el elemento (
hash & (tamaño_del_array - 1)). - El elemento (par "clave-valor" en forma de objeto
Node) se coloca en ese bucket. Si el bucket ya contiene elementos, el nuevo elemento se añade al inicio de la lista enlazada o árbol. - Al agregar, se verifica si la clave ya existe: se usa el método
equals()para comparar las claves dentro del bucket. Si la clave se encuentra, se actualiza el valor.
- Se calcula el código hash de la clave (
- Al obtener un elemento (
get):- También se calcula el índice del bucket por la clave.
- Dentro del bucket, se busca el elemento por la clave, usando los métodos
hashCode()yequals(). - Se devuelve el valor asociado.
Organización:
- Colisiones: Si varias claves tienen el mismo código hash y caen en un mismo bucket, los elementos se almacenan en forma de lista enlazada. Desde Java 8, cuando el número de elementos en un bucket supera un umbral (usualmente 8), la lista enlazada se transforma en un árbol para búsquedas más rápidas (O(log n) en lugar de O(n)).
- Redimensionamiento: Cuando el número de elementos supera el "factor de carga" (
load factor * capacity), HashMap aumenta el tamaño del array interno de buckets (usualmente al doble) y vuelve a hashear todos los elementos. Es una operación costosa (O(n)). - Parámetros:
capacity: tamaño inicial del array de buckets (por defecto 16).load factor: umbral de carga (por defecto 0.75). Define cuándo se realiza el redimensionamiento.
Por qué son importantes hashCode() y equals():
- La correcta operación de HashMap depende de la correcta implementación de estos métodos.
- Si
equals()devuelvetruepara dos objetos, entonceshashCode()debe devolver el mismo valor. - Una implementación incorrecta puede hacer que los elementos no se encuentren (
getdevolveránull), incluso si fueron añadidos.
Ejemplo de estructura Node:
// Solo interno
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
HashMap ofrece inserciones, obtenciones y eliminaciones rápidas (en promedio O(1)) cuando la distribución de hashes es uniforme. En el peor caso (colisiones fuertes), la operación puede ser O(n) o O(log n) con árboles.
No es seguro para hilos. Para uso en entornos concurrentes, se debe usar ConcurrentHashMap o Collections.synchronizedMap(new HashMap<...>(...)).