W3docs

Java StringBuilder

Crea cadenas mutables eficientemente en Java con StringBuilder: append, insert, reverse y más.

Un String es inmutable; crecer uno con += en un bucle es cuadrático. StringBuilder es la respuesta de la biblioteca estándar: un único objeto con un buffer interno redimensionable que permite añadir, insertar, eliminar e invertir contenido en su lugar, para luego convertirlo en un String inmutable al final. Es la herramienta fundamental detrás de todos los patrones modernos de construcción de cadenas en el JDK, y la opción correcta en cuanto te encuentras acumulando texto en un bucle o a través de llamadas a métodos.

Construir un builder

StringBuilder sb = new StringBuilder();             // empty, capacity 16
StringBuilder withCap = new StringBuilder(1024);    // empty, preallocated capacity
StringBuilder fromText = new StringBuilder("hi ");  // capacity = length + 16

El constructor sin argumentos comienza con capacidad 16, lo cual está bien para resultados cortos y es una elección pesimista para los largos. Si sabes aproximadamente el tamaño del string final, pasa la capacidad desde el principio — cada expansión evitada es una asignación de array menos y un arraycopy menos. new StringBuilder(estimatedLength) es la microoptimización más efectiva de toda esta parte del libro.

Encadenamiento: cada mutador retorna this

Cada método mutador de StringBuilder retorna el propio builder, de modo que las llamadas se componen en una sola expresión:

String greeting = new StringBuilder()
    .append("Hello, ")
    .append(name)
    .append('!')
    .append('\n')
    .toString();

Esto es una convención, no magia; podrías separarlo en múltiples sentencias sin ninguna diferencia funcional. El estilo encadenado refleja cómo el compilador reescribe las cadenas de + internamente, lo cual es precisamente la razón por la que la forma encadenada resulta natural en Java.

La familia de mutadores

StringBuilder tiene una superficie pequeña y enfocada:

  • append — añade texto o cualquier tipo primitivo al final. Sobrecargado para todos los primitivos, char[], CharSequence y Object (llama a toString).
  • insert(offset, ...) — las mismas sobrecargas, pero en una posición arbitraria.
  • delete(start, end), deleteCharAt(i) — elimina un rango o un único carácter.
  • replace(start, end, replacement) — reemplaza un rango con una nueva subcadena; las longitudes pueden diferir.
  • reverse() — invierte el buffer en su lugar.
  • setCharAt(i, ch) — reescribe un único carácter.
  • setLength(n) — trunca (o rellena con ) hasta n caracteres.

Estos métodos editan el buffer; no retornan un nuevo String. Para obtener una instantánea del contenido como string inmutable, llama a toString().

Inspeccionar y convertir

  • length() — número actual de caracteres.
  • capacity() — tamaño actual del array interno. Siempre ≥ length().
  • charAt(i), substring(start) / substring(start, end) — acceso de lectura, idéntico a String.
  • indexOf(s), lastIndexOf(s) — localiza una subcadena.
  • toString() — produce un String inmutable. Llámalo una sola vez al final; llamarlo repetidamente durante una construcción es una asignación innecesaria.
  • ensureCapacity(n) — hace crecer previamente el buffer hasta al menos n.
  • trimToSize() — reduce el buffer para ajustarse al contenido actual. Rara vez se necesita.

Cómo crece el buffer

Internamente, StringBuilder mantiene un byte[] (o char[] en JDKs más antiguos). Cuando un append desbordaría el buffer, este se reasigna a aproximadamente 2 × oldCapacity + 2, y el contenido anterior se copia. Cada expansión es O(n) en el tamaño actual, pero el patrón de duplicación hace que el costo total de n appends sea O(n) amortizado — muy diferente a la concatenación repetida de String, donde el mismo bucle es O(n²).

append "a" — capacity 16, length 1
... 15 more — capacity 16, length 16
append "b" — grow to 34, length 17
... 17 more — capacity 34, length 34
append "c" — grow to 70, length 35

Si conoces la longitud final, te saltas todas esas reasignaciones construyendo directamente con esa capacidad.

StringBuilder vs el operador +

Para ensamblaje de strings cortos y estáticamente conocidos, el compilador hace lo correcto por sí solo. "Hello, " + name + "!" se reescribe en tiempo de compilación como una sola cadena de StringBuilder o como una llamada a StringConcatFactory.makeConcatWithConstants (Java 9+). Ambas son eficientes. No necesitas gestionar estas expresiones de manera manual.

El patrón a evitar es += dentro de un bucle sobre un número desconocido de iteraciones:

// O(n²) — every += allocates a new String holding everything seen so far
String out = "";
for (String token : tokens) {
  out += token + "|";
}

// O(n) — one buffer, one final String
StringBuilder sb = new StringBuilder();
for (String token : tokens) {
  sb.append(token).append('|');
}
String out = sb.toString();

Si el bucle se ejecuta unas pocas veces, la diferencia es invisible. Con unos pocos miles de tokens, es un problema medible en benchmarks. Con un millón, la primera forma se cuelga y la segunda tarda milisegundos.

StringBuilder no es thread-safe

StringBuilder omite deliberadamente la sincronización para ser rápido en el caso predominantemente monohilo. Si dos hilos hacen append al mismo builder simultáneamente, los resultados son indefinidos: escrituras perdidas, caracteres sobreescritos, o una ArrayIndexOutOfBoundsException desde la ruta de expansión. Para el caso excepcional en que un builder se comparte entre hilos, usa su gemelo sincronizado — StringBuffer — en su lugar. En la práctica casi nunca se comparte un builder; cada hilo construye el suyo propio.

Un ejemplo completo

El programa siguiente usa todas las partes de la superficie que importan en el código cotidiano: append encadenado, un insert explícito, un replace que cambia la longitud de un rango, un reverse, y un único toString final. La capacidad se imprime al inicio y al final para hacer visible la duplicación del buffer.

java— editable, runs on the server

Observa los números de capacidad en la salida (capacity=32 al inicio, capacity=66 al final). Cada append de la cadena cabe dentro de la capacidad inicial de 32 — el string construido tiene solo 24 caracteres. El insert y el replace empujan entonces la longitud hasta 40, que desborda 32 y desencadena exactamente una expansión (a 2 × 32 + 2 = 66). Ajustar el tamaño más cerca de la longitud final real — new StringBuilder(48) en este caso — habría evitado esa única reasignación. Ese es todo el juego: cuanto mejor sea tu estimación de capacidad, menos eventos de copia en la expansión tendrás.

Qué sigue

StringBuilder es rápido porque no es thread-safe. Su gemelo sincronizado es la respuesta correcta en el caso (excepcional) en que realmente quieras un buffer compartido entre hilos — la misma API, pero con métodos que bloquean. Continúa con Java StringBuffer.

Práctica

Práctica
¿Por qué se prefiere `StringBuilder` sobre la concatenación de `String` en un bucle?
¿Por qué se prefiere `StringBuilder` sobre la concatenación de `String` en un bucle?
Was this page helpful?