Delete key in map: cómo funciona bajo el capó en Go
\n\n¿Alguna vez te has preguntado qué sucede realmente cuando escribes delete(myMap, "key") en Go? ¿La clave simplemente desaparece de la memoria? ¿O queda un "hueco"? ¿Y qué pasa si eliminas una clave de un map nil? Si te has hecho estas preguntas, no estás solo. El tema de delete key in map ha acumulado miles de visitas en Stack Overflow, y hoy vamos a mirar bajo el capó de esta acción aparentemente simple.
En este artículo SEO, analizaremos cómo está estructurada la tabla hash en Go (estructura hmap), cómo funciona la eliminación de una clave, dónde va la memoria y por qué no debes temer a delete en los bucles. ¡Allá vamos!
Breve introducción: cómo está estructurado el map en Go
\n\nAntes de hablar sobre la eliminación, recordemos cómo map golang almacena datos. Internamente, es una tabla hash, la estructura hmap. Consiste en un array de "cubos" (buckets), cada uno de los cuales puede almacenar hasta 8 pares clave-valor. Cuando agregas una clave, Go calcula el hash, encuentra el cubo y coloca el par dentro. Si el cubo se desborda, se crea una cadena de desbordamiento (overflow bucket).
Esta es una implementación clásica, pero con un par de trucos de los que hablaremos a continuación.
\n\nEstructura de hmap (simplificada)
\n\ntype hmap struct {\n count int // número de elementos\n flags uint8\n B uint8 // logaritmo del número de cubos\n buckets unsafe.Pointer // puntero al array de cubos\n oldbuckets unsafe.Pointer // durante el crecimiento del map\n nevacuate uintptr\n // ...\n}\n\nClave: buckets es un array continuo. La eliminación no comprime este array, solo marca la celda como "vacía".
Delete key in map: cómo funciona a nivel de código
\n\nLa llamada delete(m, k) en Go es una función incorporada que se compila en una llamada al runtime. Internamente, ocurre aproximadamente lo siguiente:
- \n
- Cálculo del hash de la clave — Go utiliza una función hash específica para el tipo de clave. \n
- Búsqueda del cubo — los bits menos significativos del hash determinan el número del cubo. \n
- Búsqueda lineal dentro del cubo (y las cadenas de desbordamiento) — encuentra la celda con la clave deseada. \n
- Limpieza de memoria — Go pone a cero la memoria de la clave y el valor para que el recolector de basura pueda recogerlos. \n
- Marcado de la celda como "vacía" — con una bandera especial
emptyRestoemptyOne. \n - Reducción del contador —
m.count--. \n
Todo esto ocurre en O(1) en promedio, pero teniendo en cuenta las colisiones, puede ser O(n) en el peor de los casos.
\n\nEjemplo de código: delete en acción
\n\npackage main\n\nimport "fmt"\n\nfunc main() {\n m := map[string]int{\n "apple": 5,\n "banana": 3,\n "cherry": 7,\n }\n\n fmt.Println("Antes de eliminar:", m)\n delete(m, "banana")\n fmt.Println("Después de eliminar:", m)\n\n // Intentemos eliminar una clave inexistente\n delete(m, "grape") // no ocurre nada, no hay error\n fmt.Println("Después de eliminar una inexistente:", m)\n}\n\nTen en cuenta: delete key in map para una clave inexistente es una operación segura. Simplemente no hace nada. Este es uno de los errores comunes entre los principiantes.
\n\n¿Qué sucede con la memoria? Mitos y realidad
\n\nMuchos desarrolladores temen que después de delete queden "huecos" en el map que provoquen fugas de memoria. Vamos a aclararlo.
Memoria para claves y valores
\n\nCuando eliminas una clave, Go pone a cero la memoria de ese elemento. Si la clave o el valor eran punteros o contenían punteros (strings, slices, maps), el recolector de basura podrá recogerlos. La celda en sí en el cubo permanece, pero está marcada como vacía.
\n\nEsto significa que el map no se reduce de tamaño en