Perché aggiungere "" a una stringa salva memoria?


193

Ho usato una variabile con molti dati, diciamo String data. Volevo usare una piccola parte di questa stringa nel modo seguente:

this.smallpart = data.substring(12,18);

Dopo alcune ore di debug (con un visualizzatore di memoria) ho scoperto che il campo degli oggetti smallpartricordava tutti i dati data, sebbene contenesse solo la sottostringa.

Quando ho cambiato il codice in:

this.smallpart = data.substring(12,18)+""; 

..il problema è stato risolto! Ora la mia applicazione usa pochissima memoria ora!

Come è possibile? Qualcuno può spiegare questo? Penso che this.smallpart abbia continuato a fare riferimento ai dati, ma perché?

AGGIORNAMENTO: Come posso cancellare la stringa grande allora? Data = new String (data.substring (0,100)) farà la cosa?


Leggi di più sul tuo intento finale di seguito: Da dove viene la stringa grande in primo luogo? Se letto da un file o database CLOB o qualcosa del genere, solo la lettura di ciò che è necessario durante l'analisi sarà ottimale tutto intorno.
PSpeed,

4
Incredibile ... Sto lavorando a Java da 4 a 5 anni, ma questo è nuovo per me :). grazie per l'informazione bro.
Parth,

1
C'è una sottigliezza nell'uso new String(String); vedi stackoverflow.com/a/390854/8946 .
Lawrence Dol,

Risposte:


159

Procedere come segue:

data.substring(x, y) + ""

crea un nuovo oggetto String (più piccolo) e getta via il riferimento alla stringa creata da substring (), consentendo così la garbage collection di questo.

La cosa importante da capire è che substring()dà una finestra su una stringa esistente - o meglio, l'array di caratteri alla base della stringa originale. Quindi consumerà la stessa memoria della stringa originale. Questo può essere vantaggioso in alcune circostanze, ma problematico se vuoi ottenere una sottostringa e smaltire la stringa originale (come hai scoperto).

Dai un'occhiata al metodo substring () nel sorgente String JDK per maggiori informazioni.

MODIFICA: per rispondere alla tua domanda aggiuntiva, la costruzione di una nuova stringa dalla sottostringa ridurrà il consumo di memoria, a condizione che bin qualsiasi riferimento alla stringa originale.

NOTA (gennaio 2013). Il comportamento sopra è cambiato in Java 7u6 . Il modello flyweight non viene più utilizzato e substring()funzionerà come ci si aspetterebbe.


89
Questo è uno dei pochissimi casi in cui il String(String)costruttore (ovvero il costruttore String che prende una stringa come input) è utile: new String(data.substring(x, y))fa effettivamente la stessa cosa di aggiungere "", ma rende l'intento in qualche modo più chiaro.
Joachim Sauer,

3
solo per la precisione, la sottostringa utilizza l' valueattributo della stringa originale. Penso che sia per questo che il riferimento è mantenuto.
Valentin Rocher,

@Bishiboosh - sì, esatto. Non volevo esporre le particolarità dell'implementazione, ma è proprio quello che sta succedendo.
Brian Agnew,

5
Tecnicamente è un dettaglio di implementazione. Tuttavia è frustrante e attira molte persone.
Brian Agnew,

1
Mi chiedo se sia possibile ottimizzare questo nel JDK usando riferimenti deboli o simili. Se sono l'ultima persona che ha bisogno di questo carattere [] e ne ho bisogno solo un po ', crea un nuovo array da utilizzare internamente.
WW.

28

Se guardi la fonte di substring(int, int), vedrai che ritorna:

new String(offset + beginIndex, endIndex - beginIndex, value);

dov'è valuel'originale char[]. Quindi ottieni una nuova stringa ma con lo stesso sottostante char[].

Quando lo fai, data.substring() + ""ottieni una nuova stringa con un nuovo sottostante char[].

In realtà, il tuo caso d'uso è l'unica situazione in cui dovresti usare il String(String)costruttore:

String tiny = new String(huge.substring(12,18));

1
C'è una sottigliezza nell'uso new String(String); vedi stackoverflow.com/a/390854/8946 .
Lawrence Dol,

17

Quando lo usi substring, in realtà non crea una nuova stringa. Fa ancora riferimento alla stringa originale, con un vincolo di offset e dimensioni.

Quindi, per consentire la raccolta della tua stringa originale, devi creare una nuova stringa (usando new Stringo cosa hai).


5

Penso che this.smallpart abbia continuato a fare riferimento ai dati, ma perché?

Poiché le stringhe Java sono costituite da un array di caratteri, un offset iniziale e una lunghezza (e un hashCode memorizzato nella cache). Alcune operazioni String come la substring()creazione di un nuovo oggetto String che condivide l'array char originale e ha semplicemente diversi campi offset e / o lunghezza. Questo funziona perché la matrice di caratteri di una stringa non viene mai modificata una volta creata.

Ciò può risparmiare memoria quando molte sottostringhe si riferiscono alla stessa stringa di base senza replicare le parti sovrapposte. Come hai notato, in alcune situazioni, può impedire che i dati non più necessari vengano raccolti.

Il modo "corretto" per risolvere questo problema è il new String(String)costruttore, vale a dire

this.smallpart = new String(data.substring(12,18));

A proposito, la migliore soluzione complessiva sarebbe quella di evitare di avere stringhe molto grandi in primo luogo e di elaborare qualsiasi input in blocchi più piccoli, alcuni KB alla volta.


C'è una sottigliezza nell'uso new String(String); vedi stackoverflow.com/a/390854/8946 .
Lawrence Dol,

5

In Java le stringhe sono oggetti imutabili e una volta creata una stringa, rimane in memoria fino a quando non viene ripulita dal garbage colector (e questa pulizia non è qualcosa che puoi dare per scontato).

Quando si chiama il metodo di sottostringa, Java non crea una nuova stringa, ma memorizza solo un intervallo di caratteri all'interno della stringa originale.

Quindi, quando hai creato una nuova stringa con questo codice:

this.smallpart = data.substring(12, 18) + ""; 

hai effettivamente creato una nuova stringa quando hai concatenato il risultato con la stringa vuota. Ecco perchè.


3

Come documentato da jwz nel 1997 :

Se si dispone di una stringa enorme, estrarre una sottostringa () di essa, aggrapparsi alla sottostringa e consentire alla stringa più lunga di diventare spazzatura (in altre parole, la sottostringa ha una durata maggiore) i byte sottostanti della stringa enorme non vanno mai lontano.


2

Per riassumere, se si creano molte sottostringhe da un piccolo numero di stringhe grandi, quindi utilizzare

   String subtring = string.substring(5,23)

Dal momento che usi solo lo spazio per memorizzare le stringhe grandi, ma se stai estraendo solo una manciata di stringhe piccole, da oggetti di stringhe grandi, allora

   String substring = new String(string.substring(5,23));

Manterrà la memoria esaurita, poiché le grandi stringhe possono essere recuperate quando non sono più necessarie.

Il fatto che tu chiami new Stringè un utile promemoria che stai davvero ricevendo una nuova stringa, piuttosto che un riferimento a quello originale.


C'è una sottigliezza nell'uso new String(String); vedi stackoverflow.com/a/390854/8946 .
Lawrence Dol,

2

Innanzitutto, la chiamata java.lang.String.substringcrea una nuova finestra sull'originaleString con l'uso dell'offset e della lunghezza anziché copiare la parte significativa dell'array sottostante.

Se osserviamo più da vicino il substringmetodo, noteremo una chiamata del costruttore di stringheString(int, int, char[]) e la passeremo intera char[]che rappresenta la stringa . Ciò significa che la sottostringa occuperà tutta la quantità di memoria della stringa originale .

Ok, ma perché + ""risulta richiesta meno memoria che senza di essa ??

Fare un +on stringsè implementato tramite la StringBuilder.appendchiamata del metodo. Guardare l'implementazione di questo metodo in AbstractStringBuilderclasse ci dirà che finalmente ha a che fare arraycopycon la parte di cui abbiamo davvero bisogno (la substring).

Qualche altra soluzione alternativa ??

this.smallpart = new String(data.substring(12,18));
this.smallpart = data.substring(12,18).intern();

0

L'aggiunta di "" a una stringa a volte consente di risparmiare memoria.

Diciamo che ho una stringa enorme che contiene un intero libro, un milione di caratteri.

Quindi creo 20 stringhe contenenti i capitoli del libro come sottostringhe.

Quindi creo 1000 stringhe contenenti tutti i paragrafi.

Quindi creo 10.000 stringhe contenenti tutte le frasi.

Quindi creo 100.000 stringhe contenenti tutte le parole.

Uso ancora solo 1.000.000 di caratteri. Se aggiungi "" a ciascun capitolo, paragrafo, frase e parola, usi 5.000.000 di caratteri.

Ovviamente è del tutto diverso se si estrae una sola parola da tutto il libro e l'intero libro potrebbe essere spazzato via, ma non perché quella parola ne contenga un riferimento.

Ed è di nuovo diverso se hai una stringa di un milione di caratteri e rimuovi le schede e gli spazi su entrambe le estremità, facendo dire 10 chiamate per creare una sottostringa. Il modo in cui Java funziona o ha evitato di copiare ogni volta un milione di caratteri. C'è un compromesso ed è positivo se sai quali sono i compromessi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.