¿Por qué la simple concatenación de cadenas en Go puede convertirse en un problema?
\n\nCualquiera que haya empezado a estudiar Go, tarde o temprano se enfrenta a la tarea: necesita unir varias cadenas en una sola. La forma más obvia es usar el operador += o la simple suma +. Y esto funciona… hasta que intentas unir mil cadenas. De repente, tu código comienza a ralentizarse y la memoria se consume como en el Black Friday.
¿Por qué sucede esto? En Go, las cadenas son inmutables. Cuando escribes s += "new", Go no agrega texto al final de la cadena antigua. Crea una nueva cadena, copiando el contenido de ambas partes. En cada operación se asigna nueva memoria. Si hay miles de estas operaciones, el recolector de basura comienza a trabajar en modo de emergencia y el rendimiento cae.
En este artículo, analizaremos todas las formas de concatenación de cadenas en Go: desde las ingenuas hasta las industriales. Aprenderás cuándo usar cada método y podrás escribir código del que no te avergüences en una revisión de código.
\n\nFormas de concatenar cadenas en Go: de lo simple a lo eficiente
\n\nConsideremos cinco enfoques principales. Para cada uno, proporcionaré un ejemplo de código y explicaré en qué situaciones es adecuado.
\n\n1. Operador + y += (forma ingenua)
\n\nEl método más simple e intuitivo. Ideal para unir 2–5 cadenas.
\n\npackage main\n\nimport "fmt"\n\nfunc main() {\n hello := "Hello"\n world := "World"\n result := hello + ", " + world + "!"\n fmt.Println(result) // Hello, World!\n}\n\n\nCuándo usarlo: solo para una pequeña cantidad de cadenas. Si estás escribiendo un bucle de 1000 iteraciones con += — detente. Esto es complejidad cuadrática en memoria (O(n²)).
2. fmt.Sprintf
\n\nEs útil cuando necesitas formatear la salida. Internamente, también crea nuevas cadenas, pero lo hace de manera más eficiente que la unión manual.
\n\nname := "Alice"\nage := 30\nresult := fmt.Sprintf("My name is %s and I am %d years old.", name, age)\nfmt.Println(result)\n\n\nCuándo usarlo: cuando necesitas no solo unir, sino formatear una cadena con diferentes tipos de datos. Para concatenación simple, no es la mejor opción debido a la sobrecarga de analizar la plantilla.
\n\n3. strings.Join — el rey para slices de cadenas
\n\nSi ya tienes un array o slice de cadenas, strings.Join es la forma más rápida y elegante.
package main\n\nimport (\n "fmt"\n "strings"\n)\n\nfunc main() {\n words := []string{"Go", "is", "awesome", "for", "backend"}\n result := strings.Join(words, " ")\n fmt.Println(result) // Go is awesome for backend\n}\n\n\nInternamente, strings.Join primero calcula la longitud total del resultado, asigna memoria una sola vez y la llena. Sin copias innecesarias.
Cuándo usarlo: siempre que tengas un slice de cadenas listo. Es la opción ideal para construir consultas SQL, cadenas CSV y URL.
\n\n4. bytes.Buffer — el clásico del género
\n\nEl paquete bytes proporciona un búfer que crece dinámicamente. Implementa la interfaz io.Writer, por lo que puedes escribir en él usando WriteString.
package main\n\nimport (\n "bytes"\n "fmt"\n)\n\nfunc main() {\n var buffer bytes.Buffer\n\n for i := 0; i < 5; i++ {\n buffer.WriteString("Line ")\n buffer.WriteString(fmt.Sprintf("%d\\n", i))\n }\n\n fmt.Println(buffer.String())\n}\n\n\nEl búfer asigna memoria en bloques (generalmente de 64 bytes), lo que reduce significativamente la cantidad de asignaciones en comparación con +=.
Cuándo usarlo: en bucles donde la cantidad de cadenas se desconoce de antemano. Por ejemplo, al generar HTML o registros.
\n\n5. strings.Builder — el estándar moderno (Go 1.10+)
\n\nA partir de Go 1.10, apareció un