W3docs

Java StringBuilder

Construa strings mutáveis com eficiência em Java usando a classe StringBuilder — append, insert, reverse e muito mais.

Uma String é imutável; crescê-la com += dentro de um loop é quadrático. StringBuilder é a resposta da biblioteca padrão: um único objeto com um buffer interno redimensionável que você manipula com append, insert, delete e reverse, para então converter em uma String imutável apenas no final. É a peça central por trás de todo padrão moderno de construção de strings no JDK, e a escolha certa assim que você se encontrar acumulando texto em um loop ou entre chamadas de métodos.

Construindo um builder

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

O construtor sem argumentos começa com capacidade 16, o que é adequado para resultados curtos e uma escolha pessimista para resultados longos. Se você sabe aproximadamente qual será o tamanho final da string, passe a capacidade logo de início — cada grow evitado é uma alocação de array a menos e um arraycopy a menos. new StringBuilder(estimatedLength) é a única microotimização mais eficaz em toda esta parte do livro.

Encadeamento: todo mutador retorna this

Todo método mutável de StringBuilder retorna o próprio builder, então as chamadas se compõem em uma única expressão:

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

Isso é convenção, não mágica; você poderia dividir em múltiplos statements sem nenhuma diferença funcional. O estilo encadeado espelha como o compilador reescreve cadeias de + internamente, o que é exatamente o motivo pelo qual a forma encadeada parece natural em Java.

A família de mutadores

StringBuilder tem uma interface pequena e focada:

  • append — adiciona texto ou qualquer primitivo ao final. Sobrecarregado para todo primitivo, char[], CharSequence e Object (chama toString).
  • insert(offset, ...) — mesmas sobrecargas, mas em uma posição arbitrária.
  • delete(start, end), deleteCharAt(i) — remove um intervalo ou um único caractere.
  • replace(start, end, replacement) — substitui um intervalo por uma nova substring; os comprimentos podem ser diferentes.
  • reverse() — inverte o buffer no lugar.
  • setCharAt(i, ch) — reescreve um único caractere.
  • setLength(n) — trunca (ou preenche com ) até n caracteres.

Esses métodos editam o buffer; eles não retornam uma nova String. Para obter um snapshot do conteúdo como uma string imutável, chame toString().

Inspecionando e convertendo

  • length() — contagem atual de caracteres.
  • capacity() — tamanho atual do array interno. Sempre ≥ length().
  • charAt(i), substring(start) / substring(start, end) — acesso de leitura, idêntico ao de String.
  • indexOf(s), lastIndexOf(s) — localiza uma substring.
  • toString() — produz uma String imutável. Chame isso uma vez no final; chamá-lo repetidamente durante a construção é alocação desperdiçada.
  • ensureCapacity(n) — pré-expande o buffer para pelo menos n.
  • trimToSize() — reduz o buffer para caber no conteúdo atual. Raramente necessário.

Como o buffer cresce

Internamente, StringBuilder mantém um byte[] (ou char[] em JDKs mais antigos). Quando um append transbordaria o buffer, ele é realocado para aproximadamente 2 × oldCapacity + 2, e o conteúdo antigo é copiado. Cada grow é O(n) no tamanho atual, mas o padrão de duplicação torna o custo total de n appends O(n) amortizado — muito diferente da concatenação repetida de String, onde o mesmo loop é 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

Se você conhece o comprimento final, evita todas essas realocações construindo diretamente com essa capacidade.

StringBuilder vs o operador +

Para montagem de strings curtas e estaticamente conhecidas, o compilador faz a coisa certa por conta própria. "Hello, " + name + "!" é reescrito em tempo de compilação em uma única cadeia de StringBuilder ou em uma chamada para StringConcatFactory.makeConcatWithConstants (Java 9+). Ambas são eficientes. Você não precisa microgerenciar essas expressões.

O padrão a evitar é += dentro de um loop sobre uma contagem desconhecida:

// 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();

Se o loop executa algumas vezes, a diferença é invisível. Com alguns milhares de tokens, é um problema mensurável em benchmarks. Com um milhão, a primeira forma trava e a segunda leva milissegundos.

StringBuilder não é thread-safe

StringBuilder omite deliberadamente a sincronização para ser rápido no caso de thread única, que é esmagadoramente o mais comum. Se duas threads fazem append no mesmo builder simultaneamente, os resultados são indefinidos: escritas perdidas, caracteres sobrescritos ou um ArrayIndexOutOfBoundsException no caminho de grow. Para o caso raro em que um builder é compartilhado entre threads, use o gêmeo sincronizado — StringBuffer — em vez disso. Na prática, você quase nunca compartilha um builder; cada thread constrói o seu próprio.

Um exemplo prático

O programa abaixo usa todas as partes da interface que importam no código do dia a dia: append encadeado, um insert explícito, um replace que altera o comprimento de um intervalo, um reverse e um toString final. A capacidade é impressa no início e no fim para tornar visível a duplicação do buffer.

java— editable, runs on the server

Observe os números de capacidade na saída (capacity=32 no início, capacity=66 no final). Todos os appends da cadeia cabem dentro da capacidade inicial de 32 — a string construída tem apenas 24 caracteres. O insert e o replace então empurram o comprimento para 40, o que ultrapassa 32 e dispara exatamente um grow (para 2 × 32 + 2 = 66). Dimensionar mais próximo do comprimento final verdadeiro — new StringBuilder(48) aqui — teria evitado essa única realocação inteiramente. É esse o jogo todo: quanto melhor a sua estimativa de capacidade, menos eventos de cópia no grow.

O que vem a seguir

StringBuilder é rápido porque não é thread-safe. Seu gêmeo sincronizado é a resposta certa no caso (raro) em que você genuinamente quer um buffer compartilhado entre threads — mesma API, métodos que travam. Continue para Java StringBuffer.

Prática

Prática
Por que `StringBuilder` é tipicamente preferido à concatenação de `String` em um loop?
Por que `StringBuilder` é tipicamente preferido à concatenação de `String` em um loop?
Was this page helpful?