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 + 16O 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[],CharSequenceeObject(chamatoString).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éncaracteres.
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 deString.indexOf(s),lastIndexOf(s)— localiza uma substring.toString()— produz umaStringimutá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 menosn.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 35Se 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.
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.