W3docs

Java StringBuilder

Costruisci stringhe mutabili in modo efficiente in Java con StringBuilder — append, insert, reverse e molto altro.

Una String è immutabile; crescerla con += in un ciclo ha costo quadratico. StringBuilder è la risposta della libreria standard: un singolo oggetto con un buffer interno ridimensionabile che puoi accodare, inserire, eliminare e invertire in-place, per poi convertirlo in una String immutabile una sola volta alla fine. È il pilastro di ogni moderno pattern di costruzione di stringhe nel JDK, e la scelta giusta nel momento in cui ti ritrovi ad accumulare testo in un ciclo o attraverso più chiamate a metodi.

Costruire 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

Il costruttore senza argomenti parte con capacità 16, che va bene per risultati brevi ma è una scelta pessimistica per quelli lunghi. Se sai approssimativamente quanto sarà grande la stringa finale, passa la capacità subito — ogni grow evitato è un'allocazione di array in meno e una arraycopy in meno. new StringBuilder(estimatedLength) è la singola micro-ottimizzazione più efficace in questa parte del libro.

Concatenazione a catena: ogni metodo mutante restituisce this

Ogni metodo che modifica StringBuilder restituisce il builder stesso, quindi le chiamate si compongono in una singola espressione:

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

Questa è convenzione, non magia; potresti spezzarla in più istruzioni separate senza alcuna differenza funzionale. Lo stile concatenato rispecchia il modo in cui il compilatore riscrive le catene di + internamente, che è proprio il motivo per cui quella forma risulta naturale da leggere in Java.

La famiglia dei metodi mutanti

StringBuilder ha un'interfaccia piccola e mirata:

  • append — aggiunge testo o qualsiasi primitivo alla fine. Con overload per ogni primitivo, char[], CharSequence e Object (chiama toString).
  • insert(offset, ...) — stessi overload, ma in una posizione arbitraria.
  • delete(start, end), deleteCharAt(i) — rimuove un intervallo o un singolo carattere.
  • replace(start, end, replacement) — sostituisce un intervallo con una nuova sottostringa; le lunghezze possono differire.
  • reverse() — inverte il buffer in-place.
  • setCharAt(i, ch) — riscrive un singolo carattere.
  • setLength(n) — tronca (o riempie con ) fino a n caratteri.

Questi metodi modificano il buffer; non restituiscono una nuova String. Per ottenere uno snapshot del contenuto come stringa immutabile, chiama toString().

Ispezione e conversione

  • length() — conteggio attuale dei caratteri.
  • capacity() — dimensione corrente dell'array interno. Sempre ≥ length().
  • charAt(i), substring(start) / substring(start, end) — accesso in lettura, identico a String.
  • indexOf(s), lastIndexOf(s) — individua una sottostringa.
  • toString() — produce una String immutabile. Chiamalo una volta sola alla fine; chiamarlo ripetutamente durante la costruzione è allocazione sprecata.
  • ensureCapacity(n) — pre-espande il buffer ad almeno n.
  • trimToSize() — riduce il buffer per adattarsi al contenuto corrente. Raramente necessario.

Come cresce il buffer

Internamente, StringBuilder contiene un byte[] (o char[] sui JDK più vecchi). Quando un append causerebbe un overflow, il buffer viene riallocato a circa 2 × oldCapacity + 2, e il vecchio contenuto viene copiato. Ogni crescita è O(n) rispetto alla dimensione corrente, ma il pattern di raddoppio rende il costo totale di n append O(n) ammortizzato — ben diverso dalla concatenazione ripetuta di String, dove lo stesso ciclo è 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 conosci la lunghezza finale, eviti tutte quelle riallocazioni costruendo il builder con quella capacità direttamente.

StringBuilder vs l'operatore +

Per l'assemblaggio di stringhe breve e staticamente noto, il compilatore fa la cosa giusta da solo. "Hello, " + name + "!" viene riscritto a tempo di compilazione in una singola catena StringBuilder o in una chiamata a StringConcatFactory.makeConcatWithConstants (Java 9+). Entrambe sono efficienti. Non hai bisogno di gestire manualmente queste espressioni.

Il pattern da evitare è += all'interno di un ciclo con un numero sconosciuto di iterazioni:

// 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 il ciclo esegue poche iterazioni, la differenza è invisibile. Con qualche migliaio di token, diventa un problema misurabile. Con un milione, la prima forma è un blocco e la seconda richiede millisecondi.

StringBuilder non è thread-safe

StringBuilder omette deliberatamente la sincronizzazione per essere veloce nel caso (di gran lunga più comune) a singolo thread. Se due thread accodano allo stesso builder contemporaneamente, i risultati sono indefiniti: scritture perse, caratteri sovrascritti o un ArrayIndexOutOfBoundsException dal percorso di crescita. Per il raro caso in cui un builder venga condiviso tra thread, usa il gemello sincronizzato — StringBuffer — al suo posto. In pratica non condividi quasi mai un builder; ogni thread costruisce il proprio.

Un esempio pratico

Il programma seguente usa ogni parte dell'interfaccia rilevante nel codice quotidiano: append concatenato, un insert esplicito, un replace che cambia la lunghezza di un intervallo, un reverse e un unico toString finale. La capacità viene stampata all'inizio e alla fine per rendere visibile il raddoppio del buffer.

java— editable, runs on the server

Osserva i valori di capacità nell'output (capacity=32 all'inizio, capacity=66 alla fine). Ogni append della catena rientra nella capacità iniziale di 32 — la stringa costruita è di soli 24 caratteri. L'insert e il replace portano poi la lunghezza a 40, che supera 32 e scatena esattamente una crescita (a 2 × 32 + 2 = 66). Pre-dimensionare più vicino alla lunghezza finale reale — new StringBuilder(48) qui — avrebbe evitato quella singola riallocazione. Questo è tutto il gioco: più accurata è la stima della capacità, meno eventi di copia-in-crescita si verificano.

Cosa c'è dopo

StringBuilder è veloce perché non è thread-safe. Il suo gemello sincronizzato è la risposta giusta nel (raro) caso in cui si voglia davvero condividere un buffer tra thread — stessa API, metodi che acquisiscono un lock. Continua con Java StringBuffer.

Pratica

Pratica
Perché `StringBuilder` è in genere preferito alla concatenazione di `String` in un ciclo?
Perché `StringBuilder` è in genere preferito alla concatenazione di `String` in un ciclo?
Was this page helpful?