StringBuilder y manipulación eficiente de cadenas

Intermedio
Java
Java
Actualizado: 18/04/2026

Por qué String es inmutable

En Java, los objetos String son inmutables: una vez creados, su contenido no puede modificarse. Cuando escribes s = s + "x", no estás mutando s; estás creando un nuevo String y reasignando la referencia. Esta decisión de diseño tiene razones importantes:

  • Thread-safety: varios hilos pueden compartir la misma String sin sincronización.
  • Caching del hashCode: se calcula una vez y se reutiliza.
  • String pool: literales iguales se reutilizan en memoria ("hola" == "hola" puede ser true).
  • Seguridad: cadenas pasadas a APIs sensibles (rutas de archivo, SQL) no pueden modificarse por terceros.

El contrapunto: cualquier operación que "cambie" un String crea un objeto nuevo. Concatenar muchas veces desperdicia memoria y tiempo.

El problema con + en bucles

Supón que quieres construir una cadena con 10 000 líneas:

String resultado = "";
for (int i = 0; i < 10_000; i++) {
    resultado += "línea " + i + "\n"; // crea un String nuevo cada iteración
}

Cada += genera internamente:

  1. Un StringBuilder temporal
  2. Un nuevo String final
  3. El String anterior queda para recolección de basura

Con 10 000 iteraciones son 10 000 objetos temporales. El rendimiento es terrible en cadenas grandes.

StringBuilder: cadenas mutables

StringBuilder es la clase estándar para construir cadenas paso a paso. Internamente usa un array de caracteres que se redimensiona según crece el contenido.

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10_000; i++) {
    sb.append("línea ").append(i).append('\n');
}
String resultado = sb.toString();

Ventajas:

  • Una sola asignación final de String (en toString()).
  • Redimensionado eficiente: duplica la capacidad al llenarse.
  • API fluida: cada método devuelve el propio StringBuilder, permitiendo encadenamiento.

Métodos principales

| Método | Función | |--------|---------| | append(Object) | Añade al final. Acepta cualquier tipo primitivo y objeto. | | insert(int offset, Object) | Inserta en una posición concreta. | | delete(int start, int end) | Elimina un rango. | | deleteCharAt(int index) | Elimina un solo carácter. | | replace(int start, int end, String str) | Reemplaza un rango por otro string. | | reverse() | Invierte la secuencia. | | charAt(int index) | Devuelve el carácter en la posición. | | setCharAt(int index, char ch) | Sustituye un carácter puntual. | | length() | Longitud actual. | | capacity() | Capacidad del buffer interno. | | ensureCapacity(int min) | Garantiza al menos min de capacidad. | | toString() | Devuelve el contenido como String inmutable. |

Ejemplo mostrando varios:

StringBuilder sb = new StringBuilder("Hola mundo");
sb.insert(4, " bello"); // "Hola bello mundo"
sb.append("!"); // "Hola bello mundo!"
sb.replace(5, 10, "grande"); // "Hola grande mundo!"
sb.deleteCharAt(sb.length()-1); // "Hola grande mundo"
sb.reverse(); // "odnum ednarg aloH"
String s = sb.toString();

Capacidad inicial

Si sabes aproximadamente cuánto crecerá la cadena, pasa la capacidad inicial al constructor para evitar redimensionados:

StringBuilder sb = new StringBuilder(1024); // reserva 1024 chars

El valor por defecto es 16. Cada vez que se llena, la capacidad se duplica (nuevaCap = (viejaCap + 1) * 2). Reservar de más es mejor que redimensionar muchas veces.

StringBuilder vs StringBuffer

StringBuffer es la versión thread-safe de StringBuilder: sus métodos están sincronizados. En código monohilo (la mayoría) StringBuilder es más rápido. Solo usa StringBuffer si varios hilos modifican la misma instancia simultáneamente, algo poco frecuente en diseños modernos.

| Aspecto | StringBuilder | StringBuffer | |---------|---------------|--------------| | Thread-safe | No | Sí (synchronized) | | Velocidad | Más rápida | Más lenta | | Disponible desde | Java 5 | Java 1.0 | | Uso recomendado | Monohilo | Multihilo compartido |

Casos de uso habituales

Construir SQL dinámico

StringBuilder sql = new StringBuilder("SELECT * FROM productos WHERE 1=1");
if (filtroNombre != null) sql.append(" AND nombre LIKE ?");
if (precioMin > 0) sql.append(" AND precio >= ?");
if (precioMax > 0) sql.append(" AND precio <= ?");
sql.append(" ORDER BY nombre");

Generar CSV

public String toCsv(List<Producto> productos) {
    StringBuilder sb = new StringBuilder(productos.size() * 64);
    sb.append("id,nombre,precio\n");
    for (Producto p : productos) {
        sb.append(p.id())
        .append(',').append(p.nombre())
        .append(',').append(p.precio())
        .append('\n');
    }
    return sb.toString();
}

Plantilla con reemplazos

StringBuilder html = new StringBuilder("""
    <div>
    <h1>__TITULO__</h1>
    <p>__CUERPO__</p>
    </div>
    """);
int i = html.indexOf("__TITULO__");
html.replace(i, i + "__TITULO__".length(), "Bienvenido");
i = html.indexOf("__CUERPO__");
html.replace(i, i + "__CUERPO__".length(), "Contenido generado");

Cuándo NO usar StringBuilder

El compilador moderno optimiza automáticamente concatenaciones simples:

String mensaje = "Hola " + nombre + ", tienes " + edad + " años.";

Esto se transforma en una sola operación eficiente (en Java 9+ usa invokedynamic con StringConcatFactory, incluso mejor que StringBuilder). No necesitas StringBuilder para concatenaciones puntuales fuera de bucles.

Regla práctica: usa StringBuilder cuando la concatenación ocurre dentro de un bucle, una recursión o condicional complejo. Para expresiones simples, deja que el compilador optimice.

Alternativas modernas

  • String.join(CharSequence delimiter, Iterable<? extends CharSequence> elements): para unir con separador.
  • Collectors.joining(): en streams.
  • String.format() / formatted(): para plantillas con placeholders.
  • Text blocks + formatted(): para cadenas largas con sustituciones.

Ejemplo con String.join:

List<String> partes = List.of("uno", "dos", "tres");
String unido = String.join(", ", partes); // "uno, dos, tres"

StringBuilder sigue siendo la herramienta correcta cuando necesitas construir incrementalmente, modificar in-place o insertar en posiciones arbitrarias.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Java es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Java

Explora más contenido relacionado con Java y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Comprender por qué String es inmutable en Java. Diferenciar String, StringBuilder y StringBuffer. Usar append, insert, delete, reverse, replace sobre StringBuilder. Medir el impacto de usar + dentro de bucles vs StringBuilder. Aplicar StringBuilder en casos reales: builders de SQL, plantillas, CSV. Elegir la capacidad inicial adecuada.