Strings, Runes y Bytes
Strings, Runes y Bytes
En Go, un string es una secuencia inmutable de bytes. Para trabajar correctamente con texto Unicode, es fundamental entender la relacion entre string, []byte y []rune.
Strings son Inmutables
Un string no puede modificarse despues de crearse. Cualquier operacion que “modifica” un string en realidad crea uno nuevo:
package main
import "fmt"
func main() {
s := "Hola"
// s[0] = 'h' // ERROR: no compila, strings son inmutables
// Para modificar, convertir a []byte
b := []byte(s)
b[0] = 'h'
s2 := string(b)
fmt.Println(s2) // "hola"
// Concatenacion crea un nuevo string
s3 := s + " mundo"
fmt.Println(s3) // "Hola mundo"
}
String vs []byte vs []rune
| Tipo | Representa | Ejemplo |
|---|---|---|
string | Secuencia inmutable de bytes | "Hola" |
[]byte | Slice mutable de bytes | []byte{72, 111, 108, 97} |
[]rune | Slice de code points Unicode | []rune{'H', 'o', 'l', 'a'} |
package main
import "fmt"
func main() {
s := "cafe\u0301" // "cafe" + acento combinante = "café"
fmt.Println("string:", s)
fmt.Println("len (bytes):", len(s)) // 6
fmt.Println("[]byte:", []byte(s)) // [99 97 102 101 204 129]
fmt.Println("[]rune:", []rune(s)) // [99 97 102 101 769]
fmt.Printf("tipo rune: %T\n", []rune(s)[0]) // int32
}
Un rune es un alias de int32 y representa un code point Unicode.
Raw Strings con Backticks
Los raw strings usan backticks y no procesan secuencias de escape:
package main
import "fmt"
func main() {
// String normal: procesa escapes
normal := "Linea 1\nLinea 2\tTabulado"
fmt.Println(normal)
// Raw string: literal, sin escapes
raw := `Linea 1\nLinea 2\tTabulado`
fmt.Println(raw) // imprime \n y \t literalmente
// Util para regex, SQL, HTML
query := `
SELECT nombre, edad
FROM usuarios
WHERE edad > 18
ORDER BY nombre
`
fmt.Println(query)
ruta := `C:\Users\artiko\documentos`
fmt.Println(ruta)
}
len() vs RuneCountInString()
len() cuenta bytes, no caracteres. Para contar caracteres Unicode usa utf8.RuneCountInString():
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
ascii := "Hello"
utf := "Hola mundo"
emoji := "Go es 🔥"
japones := "日本語"
fmt.Printf("%-15s len=%d runes=%d\n", ascii,
len(ascii), utf8.RuneCountInString(ascii))
fmt.Printf("%-15s len=%d runes=%d\n", utf,
len(utf), utf8.RuneCountInString(utf))
fmt.Printf("%-15s len=%d runes=%d\n", emoji,
len(emoji), utf8.RuneCountInString(emoji))
fmt.Printf("%-15s len=%d runes=%d\n", japones,
len(japones), utf8.RuneCountInString(japones))
}
Salida:
Hello len=5 runes=5
Hola mundo len=10 runes=10
Go es 🔥 len=10 runes=7
日本語 len=9 runes=3
Iterar: range (Runes) vs Indice (Bytes)
package main
import "fmt"
func main() {
s := "café"
// Por indice: itera bytes (puede romper caracteres multi-byte)
fmt.Println("=== Por bytes ===")
for i := 0; i < len(s); i++ {
fmt.Printf("byte[%d] = %x\n", i, s[i])
}
// Con range: itera runes (correcto para Unicode)
fmt.Println("\n=== Por runes ===")
for i, r := range s {
fmt.Printf("rune[%d] = %c (U+%04X)\n", i, r, r)
}
}
El range sobre un string decodifica automaticamente UTF-8 y entrega cada rune con su posicion en bytes.
Paquete strings
El paquete strings provee funciones para manipular strings:
package main
import (
"fmt"
"strings"
)
func main() {
s := " Go es simple y poderoso "
// Busqueda
fmt.Println(strings.Contains(s, "simple")) // true
fmt.Println(strings.HasPrefix(s, " Go")) // true
fmt.Println(strings.HasSuffix(s, "poderoso ")) // true
fmt.Println(strings.Count(s, "o")) // 3
fmt.Println(strings.Index(s, "simple")) // 8
// Transformacion
fmt.Println(strings.ToUpper("hola")) // "HOLA"
fmt.Println(strings.ToLower("MUNDO")) // "mundo"
fmt.Println(strings.TrimSpace(s)) // "Go es simple y poderoso"
fmt.Println(strings.Trim("***hola***", "*")) // "hola"
// Split y Join
partes := strings.Split("a,b,c,d", ",")
fmt.Println(partes) // [a b c d]
unido := strings.Join(partes, " - ")
fmt.Println(unido) // "a - b - c - d"
// Reemplazo
fmt.Println(strings.Replace("foo bar foo", "foo", "baz", 1)) // "baz bar foo"
fmt.Println(strings.ReplaceAll("foo bar foo", "foo", "baz")) // "baz bar baz"
// Repeticion
fmt.Println(strings.Repeat("Go! ", 3)) // "Go! Go! Go! "
}
Resumen de funciones strings
| Funcion | Descripcion |
|---|---|
Contains(s, sub) | Contiene substring |
HasPrefix(s, pre) | Empieza con prefijo |
HasSuffix(s, suf) | Termina con sufijo |
Split(s, sep) | Divide en slice |
Join(sl, sep) | Une slice en string |
Replace(s, old, new, n) | Reemplaza n ocurrencias |
ReplaceAll(s, old, new) | Reemplaza todas |
TrimSpace(s) | Elimina espacios extremos |
ToUpper(s) / ToLower(s) | Cambia mayusculas/minusculas |
Count(s, sub) | Cuenta ocurrencias |
Paquete strconv
Convierte entre strings y tipos numericos:
package main
import (
"fmt"
"strconv"
)
func main() {
// Int a String
s := strconv.Itoa(42)
fmt.Println(s) // "42"
// String a Int
n, err := strconv.Atoi("123")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(n) // 123
// String a Int con error
_, err = strconv.Atoi("abc")
fmt.Println(err) // strconv.Atoi: parsing "abc": invalid syntax
// Float a String
sf := strconv.FormatFloat(3.14159, 'f', 2, 64)
fmt.Println(sf) // "3.14"
// String a Float
f, err := strconv.ParseFloat("2.718", 64)
if err == nil {
fmt.Println(f) // 2.718
}
// Bool
fmt.Println(strconv.FormatBool(true)) // "true"
b, _ := strconv.ParseBool("true")
fmt.Println(b) // true
}
strings.Builder
Para concatenar muchos strings eficientemente, usa strings.Builder en lugar del operador +:
package main
import (
"fmt"
"strings"
)
func main() {
// Ineficiente: cada + crea un nuevo string
resultado := ""
for i := 0; i < 5; i++ {
resultado += fmt.Sprintf("item-%d ", i)
}
fmt.Println(resultado)
// Eficiente: Builder usa un buffer interno
var b strings.Builder
for i := 0; i < 5; i++ {
fmt.Fprintf(&b, "item-%d ", i)
}
fmt.Println(b.String())
}
La diferencia de rendimiento es significativa con muchas concatenaciones: + es O(n^2) mientras que Builder es O(n).
Ejemplo Completo: Procesador de Texto
package main
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
func contarPalabras(texto string) int {
return len(strings.Fields(texto))
}
func capitalizarPalabras(texto string) string {
palabras := strings.Fields(texto)
var b strings.Builder
for i, p := range palabras {
if i > 0 {
b.WriteByte(' ')
}
runes := []rune(p)
runes[0] = unicode.ToUpper(runes[0])
b.WriteString(string(runes))
}
return b.String()
}
func main() {
texto := " go es un lenguaje simple y eficiente "
fmt.Println("Original:", texto)
fmt.Println("Limpio:", strings.TrimSpace(texto))
fmt.Println("Palabras:", contarPalabras(texto))
fmt.Println("Capitalizado:", capitalizarPalabras(texto))
fmt.Println("Runes:", utf8.RuneCountInString(texto))
}
Ejercicios
- Escribe una funcion que invierta un string respetando caracteres Unicode multi-byte
- Crea una funcion
esPalindromo(s string) boolque ignore mayusculas y espacios - Implementa un contador de frecuencia de caracteres usando
map[rune]int
Resumen
- Strings son secuencias inmutables de bytes codificados en UTF-8
[]bytepara manipulacion a nivel de bytes,[]runepara caracteres Unicodelen()cuenta bytes;utf8.RuneCountInString()cuenta runesrangesobre string itera runes; indice itera bytes- Paquete
stringspara busqueda, transformacion, split/join - Paquete
strconvpara conversiones string-numero strings.Builderpara concatenacion eficiente