Concorrenza
Java è stato definito dall'inizio con considerazioni sulla concorrenza. Come spesso menzionato, i mutabili condivisi sono problematici. Una cosa può cambiarne un'altra dietro il dorso di un altro thread senza che quel thread ne sia consapevole.
Esistono numerosi bug C ++ multithread che sono emersi a causa di una stringa condivisa - in cui un modulo ha ritenuto che fosse sicuro cambiare quando un altro modulo nel codice aveva salvato un puntatore su di esso e si aspettava che rimanesse lo stesso.
La 'soluzione' a questo è che ogni classe crea una copia difensiva degli oggetti mutabili che gli vengono passati. Per stringhe mutabili, questo è O (n) per fare la copia. Per stringhe immutabili, fare una copia è O (1) perché non è una copia, è lo stesso oggetto che non può cambiare.
In un ambiente multithread, gli oggetti immutabili possono sempre essere condivisi in modo sicuro tra loro. Ciò comporta una riduzione complessiva dell'utilizzo della memoria e migliora la memorizzazione nella memoria cache.
Sicurezza
Molte volte le stringhe vengono inviate come argomenti ai costruttori: connessioni di rete e protocolli sono i due che vengono in mente più facilmente. Essere in grado di cambiarlo in un momento indeterminato più tardi nell'esecuzione può portare a problemi di sicurezza (la funzione pensava che si stesse collegando a una macchina, ma era deviata su un'altra, ma tutto nell'oggetto sembra che fosse collegato alla prima ... è anche la stessa stringa).
Java permette di usare la riflessione - e i parametri per questo sono stringhe. Il pericolo di passare una stringa che può essere modificata attraverso un altro metodo che riflette. Questo è molto brutto.
Chiavi per l'hash
La tabella hash è una delle strutture di dati più utilizzate. Le chiavi della struttura dei dati sono spesso stringhe. Avere stringhe immutabili significa che (come sopra) la tabella hash non ha bisogno di fare una copia della chiave hash ogni volta. Se le stringhe fossero mutabili e la tabella hash non lo facesse, sarebbe possibile che qualcosa cambi la chiave hash a distanza.
Il modo in cui funziona l'Oggetto in Java è che tutto ha una chiave hash (accessibile tramite il metodo hashCode ()). Avere una stringa immutabile significa che l'hashCode può essere memorizzato nella cache. Considerando la frequenza con cui le stringhe vengono utilizzate come chiavi di un hash, ciò fornisce un significativo incremento delle prestazioni (piuttosto che dover ricalcolare il codice hash ogni volta).
sottostringhe
Avendo la stringa immutabile, anche l'array di caratteri sottostante che supporta la struttura dei dati è immutabile. Ciò consente alcune ottimizzazioni sul substring
metodo da eseguire (non sono necessariamente eseguite, ma introduce anche la possibilità di alcune perdite di memoria).
Se fate:
String foo = "smiles";
String bar = foo.substring(1,5);
Il valore di bar
è 'miglio'. Tuttavia, entrambi foo
e bar
possono essere supportati dallo stesso array di caratteri, riducendo l'istanza di più array di caratteri o copiandolo, semplicemente usando punti di inizio e fine diversi all'interno della stringa.
pippo | | (0, 6)
vv
sorrisi
^ ^
bar | | (1, 5)
Ora, il lato negativo di ciò (la perdita di memoria) è che se uno avesse una stringa lunga 1k e prendesse la sottostringa del primo e del secondo carattere, sarebbe anche supportato dalla matrice di caratteri lunga 1k. Questo array rimarrebbe in memoria anche se la stringa originale che aveva un valore dell'intero array di caratteri fosse garbage collection.
Si può vedere questo in String da JDK 6b14 (il seguente codice proviene da una fonte GPL v2 e usato come esempio)
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.offset = 0;
this.count = count;
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
// Package private constructor which shares value array for speed.
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}
Notare come la sottostringa utilizza il costruttore di stringhe a livello di pacchetto che non comporta alcuna copia dell'array e sarebbe molto più veloce (a scapito della possibilità di mantenerlo attorno ad alcuni array di grandi dimensioni, anche se non duplicare array di grandi dimensioni).
Si noti che il codice sopra riportato è per Java 1.6. Il modo in cui viene implementato il costruttore di sottostringa è stato modificato con Java 1.7 come documentato in Modifiche alla rappresentazione interna di String fatta in Java 1.7.0_06 - il problema derivante
dalla perdita di memoria che ho menzionato sopra. Java probabilmente non è stato visto come un linguaggio con molta manipolazione delle stringhe e quindi l'incremento delle prestazioni per una sottostringa è stata una buona cosa. Ora, con enormi documenti XML archiviati in stringhe che non vengono mai raccolte, questo diventa un problema ... e quindi il passaggio al String
non utilizzo dello stesso array sottostante con una sottostringa, in modo che l'array di caratteri più grande possa essere raccolto più rapidamente.
Non abusare della pila
Si potrebbe passare il valore della stringa in giro invece del riferimento alla stringa immutabile per evitare problemi con la mutabilità. Tuttavia, con stringhe di grandi dimensioni, passare questo nello stack sarebbe ... offensivo per il sistema (mettere interi documenti XML come stringhe nello stack e quindi rimuoverli o continuare a passarli lungo ...).
La possibilità di deduplicazione
Certo, questa non era una motivazione iniziale per cui le stringhe dovrebbero essere immutabili, ma quando si guarda il razionale del perché le stringhe immutabili sono una buona cosa, questo è certamente qualcosa da considerare.
Chiunque abbia lavorato un po 'con Strings sa di poter succhiare la memoria. Ciò è particolarmente vero quando stai facendo cose come estrarre i dati dai database che rimangono in sospeso per un po '. Molte volte con queste punture, sono sempre la stessa stringa (una volta per ogni riga).
Molte applicazioni Java su larga scala sono attualmente strozzate nella memoria. Le misurazioni hanno dimostrato che circa il 25% del set di dati live heap Java in questi tipi di applicazioni viene utilizzato dagli oggetti String. Inoltre, circa la metà di quegli oggetti String sono duplicati, dove duplicati significa string1.equals (string2) è vero. Avere oggetti String duplicati nell'heap è essenzialmente solo uno spreco di memoria. ...
Con l'aggiornamento 8 di Java 8, JEP 192 (motivazione citata sopra) viene implementato per risolvere questo problema. Senza entrare nei dettagli di come funziona la deduplicazione delle stringhe, è essenziale che le stringhe stesse siano immutabili. Non puoi deduplicare StringBuilders perché possono cambiare e non vuoi che qualcuno cambi qualcosa da sotto di te. Le stringhe immutabili (relative a quel pool di stringhe) indicano che puoi passare attraverso e se trovi due stringhe uguali, puoi puntare un riferimento di stringa all'altro e lasciare che il garbage collector consumi quello appena inutilizzato.
Altre lingue
L'obiettivo C (che precede Java) ha NSString
e NSMutableString
.
C # e .NET hanno fatto le stesse scelte di progettazione della stringa predefinita essendo immutabile.
Anche le stringhe Lua sono immutabili.
Anche Python .
Storicamente, Lisp, Scheme, Smalltalk internano la stringa e quindi la rendono immutabile. I linguaggi dinamici più moderni usano spesso le stringhe in qualche modo che richiedono che siano immutabili (potrebbe non essere una stringa , ma è immutabile).
Conclusione
Queste considerazioni di progettazione sono state fatte ancora e ancora in una moltitudine di lingue. È opinione generale che le stringhe immutabili, nonostante tutto il loro disagio, siano migliori delle alternative e portino a un codice migliore (meno bug) e agli eseguibili più veloci in generale.