¿Por qué tu código podría ralentizarse sin motivo aparente?
\nImagine: ha escrito un algoritmo perfecto, ha pasado todas las pruebas unitarias, pero en producción la aplicación de repente comienza a "ir lenta". La mayoría de las veces, la culpa no es del algoritmo en sí, sino de cómo recorre las colecciones. Esto se aplica especialmente a Java Map, una estructura de datos que se encuentra en 9 de cada 10 proyectos corporativos.
\nLa pregunta "How do I efficiently iterate over each entry in a Java Map?" en Stack Overflow ha acumulado decenas de miles de visitas no por casualidad. Muchos desarrolladores, incluso con 2-3 años de experiencia, utilizan enfoques no óptimos. Analicemos cómo recorrer un Map correctamente y por qué un método puede ser 10 veces más rápido que otro.
\n\nAnatomía de Java Map: ¿qué hay en su interior?
\nAntes de hablar de iteración, recordemos su estructura. HashMap es una tabla hash con cubos. TreeMap es un árbol rojo-negro. LinkedHashMap es un híbrido con una lista doblemente enlazada. Cada una de estas estructuras almacena pares key-value, pero las formas de recorrerlas difieren en velocidad y consumo de memoria.
Las principales colecciones de vista (view collections) que proporciona Map:
\n- \n
keySet()— conjunto de claves \n values()— colección de valores \n entrySet()— conjunto de pares (clave-valor) \n
Es a través de estas tres "ventanas" que veremos los datos.
\n\nMétodo n.º 1: entrySet clásico con for-each (el más popular)
\nEste es un patrón básico pero muy eficiente. Obtiene acceso tanto a la clave como al valor en una sola iteración.
\nMap<String, Integer> map = new HashMap<>();\n// ... llenar el map\nfor (Map.Entry<String, Integer> entry : map.entrySet()) {\n String key = entry.getKey();\n Integer value = entry.getValue();\n // su lógica\n}\n¿Por qué es eficiente? Porque entrySet() devuelve una view (vista), no una copia de los datos. El iterador recorre los nodos internos (Node) del HashMap, y cada llamada a next() proporciona inmediatamente tanto la clave como el valor. No hay búsqueda adicional por hash.
Cuándo usarlo: siempre que necesite ambos componentes del par. Es el estándar de facto en la comunidad Java.
\n\nMétodo n.º 2: keySet + get (antipatrón que debe evitarse)
\nMuchos principiantes escriben así:
\nfor (String key : map.keySet()) {\n Integer value = map.get(key);\n // lógica\n}\nA primera vista, el código es legible. Pero internamente, HashMap.get() recalcula el hash de la clave y recorre la cadena de colisiones cada vez. Para 100 elementos es imperceptible, pero para 100 000 ya es una degradación grave. En el peor caso (muchas colisiones), la complejidad pasa de O(n) a O(n²).
\nConclusión: si necesita tanto la clave como el valor, use entrySet(). Si solo necesita las claves, keySet() es ideal.
Método n.º 3: Iterador con eliminación explícita (para mutaciones durante el recorrido)
\nA veces es necesario eliminar elementos de un Map durante la iteración. El bucle for-each lanzará una ConcurrentModificationException. Aquí es donde el iterador viene al rescate:
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();\nwhile (iterator.hasNext()) {\n Map.Entry<String, Integer> entry = iterator.next();\n if (entry.getValue() < 0) {\n iterator.remove(); // eliminación segura\n }\n}\nEste método es especialmente útil cuando filtra datos "sobre la marcha". El iterador funciona en O(1) por cada eliminación en un HashMap, ya que conoce el nodo actual.
\n\nMétodo n.º 4: forEach con lambda (Java 8+)
\nA partir de Java 8, Map tiene su propio método forEach:
map.forEach((key, value) -> {\n System.out.println(key + " -> " + value);\n});\nBajo el capó