Saltar a contenido

HashMap

HashMap implementa la interfaz Map usando una tabla hash. Almacena pares clave-valor con acceso rápido por clave.

Características principales

Aspecto Detalle
Estructura Array de buckets (listas enlazadas o árboles)
Claves Únicas, basadas en hashCode() y equals()
Valores Pueden ser duplicados
Operaciones get, put, remove: O(1) promedio
Null Permite una clave null y múltiples valores null
Orden No garantiza orden

Cuándo usar HashMap

Ideal para: - Cachés en memoria (lookup rápido) - Índices de búsqueda - Contadores de frecuencia - Almacenar configuraciones - Sesiones de usuario

Evitar cuando: - Necesitas orden por clave (usa TreeMap) - Necesitas orden de inserción (usa LinkedHashMap) - Concurrencia (usa ConcurrentHashMap)

Ejemplos de uso

Caché simple

Map<String, User> userCache = new HashMap<>();

// Almacenar
userCache.put("user123", new User("John", "john@email.com"));

// Recuperar
User user = userCache.get("user123");

// Verificar existencia
if (userCache.containsKey("user123")) {
    // Usuario en caché
}

Contador de frecuencia

String texto = "manzana pera manzana naranja pera manzana";
Map<String, Integer> frecuencias = new HashMap<>();

for (String palabra : texto.split(" ")) {
    frecuencias.merge(palabra, 1, Integer::sum);
    // Equivalente a:
    // frecuencias.put(palabra, frecuencias.getOrDefault(palabra, 0) + 1);
}

// Resultado: {manzana=3, pera=2, naranja=1}

Agrupar elementos

List<Person> personas = Arrays.asList(
    new Person("Ana", "Madrid"),
    new Person("Luis", "Barcelona"),
    new Person("María", "Madrid")
);

Map<String, List<Person>> porCiudad = new HashMap<>();
for (Person p : personas) {
    porCiudad.computeIfAbsent(p.getCiudad(), k -> new ArrayList<>()).add(p);
}

// Resultado:
// Madrid → [Ana, María]
// Barcelona → [Luis]

Métodos útiles

Java 8+ métodos funcionales

Map<String, Integer> scores = new HashMap<>();

// putIfAbsent: solo si la clave no existe
scores.putIfAbsent("Player1", 0); // Agrega
scores.putIfAbsent("Player1", 100); // Ignora, ya existe

// compute: calcula nuevo valor basado en clave y valor actual
scores.compute("Player1", (k, v) -> v == null ? 1 : v + 10);

// computeIfAbsent: computa solo si no existe
scores.computeIfAbsent("Player2", k -> loadScoreFromDB(k));

// computeIfPresent: computa solo si existe
scores.computeIfPresent("Player1", (k, v) -> v + bonus);

// merge: combina valores
scores.merge("Player1", 50, Integer::sum); // Suma 50 al valor existente

// forEach: iterar con BiConsumer
scores.forEach((player, score) -> 
    System.out.println(player + ": " + score)
);

// getOrDefault: valor por defecto si no existe
int score = scores.getOrDefault("Unknown", 0);

Iteración

Map<String, Integer> map = new HashMap<>();

// 1. Iterar sobre entries (más eficiente)
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey() + " = " + entry.getValue());
}

// 2. Java 8+ forEach
map.forEach((k, v) -> System.out.println(k + " = " + v));

// 3. Solo claves
for (String key : map.keySet()) {
    System.out.println(key);
}

// 4. Solo valores
for (Integer value : map.values()) {
    System.out.println(value);
}

Implementación en el proyecto

// HashMapExample.java demuestra:
public class HashMapExample {
    private final Map<String, String> map = new HashMap<>();

    public void put(String key, String value) {
        map.put(key, value);
    }

    public String get(String key) {
        return map.get(key);
    }
}

Escenarios BDD

Escenario: Almacenar y recuperar valor
  Dado un HashMap vacío
  Cuando agrego "clave" → "valor"
  Entonces obtener "clave" retorna "valor"

Escenario: Sobrescribir valor existente
  Dado un HashMap con "clave" → "valor1"
  Cuando agrego "clave" → "valor2"
  Entonces obtener "clave" retorna "valor2"

Escenario: Verificar existencia de clave
  Dado un HashMap con "existente" → "valor"
  Entonces containsKey("existente") es true
  Y containsKey("inexistente") es false

Funcionamiento interno

Clave → hashCode() → bucket index

Bucket 0: null
Bucket 1: Entry("A", "valorA") → Entry("B", "valorB") [colisión]
Bucket 2: Entry("C", "valorC")
Bucket 3: null
  • Java 7: Lista enlazada en colisiones
  • Java 8+: Árbol binario si hay muchas colisiones (>8 elementos)

Rendimiento

Operación Complejidad Notas
put() O(1) O(log n) en árbol si muchas colisiones
get() O(1) O(log n) en árbol si muchas colisiones
remove() O(1) O(log n) en árbol si muchas colisiones
containsKey() O(1) Igual que get()
containsValue() O(n) Debe recorrer todos los valores
Iteración O(n) n = capacidad + número de elementos

HashMap vs Hashtable vs ConcurrentHashMap

Característica HashMap Hashtable ConcurrentHashMap
Thread-safe No
Performance Alta Baja Alta
Null keys Sí (una) No No
Null values No No
Iterator Fail-fast Fail-fast Weakly consistent

Conclusión

HashMap es la implementación de Map más usada por su simplicidad y rendimiento. Úsala cuando: - No necesitas orden - No hay concurrencia - Requieres acceso rápido O(1) por clave