2013-04-20 15 views
25

Fino a Java 6, avevamo una sottostringa a tempo costante su String. In Java 7, perché hanno deciso di adottare la copia della serie char - e di degradare in complessità lineare di tempo - quando qualcosa come StringBuilder era esattamente pensato per quello?Java 7 String - complessità della sottostringa

+6

Per evitare che una stringa di lunghezza ridotta prevenga la garbage collection di un 'char []' arbitrariamente grande. –

+0

L'uso di 'StringBuilder' dovrebbe risolvere un problema del genere, non è vero? – anoopelias

+4

L'uso di 'StringBuilder' consente di aggirare il problema una volta che si è consapevoli che esiste. Tuttavia, non risolve le perdite di memoria nel codice esistente. Questa modifica corregge le perdite di memoria nel codice esistente e, dal momento che le copie del buffer sono generalmente supportate dall'hardware, finisce per non costare il tempo lineare per le sottostringhe che rientrano in una pagina di memoria virtuale. –

risposta

26

perché hanno deciso è discusso in Oracle bug #4513622 : (str) keeping a substring of a field prevents GC for object:

Quando si chiama String.substring come nell'esempio, un nuovo array di caratteri per l'archiviazione non allocata. Usa l'array di caratteri della stringa originale. Pertanto, l'array di caratteri che supporta la stringa originale non può essere convertito in GC finché i riferimenti della sottostringa non possono essere anche GC. Si tratta di un'ottimizzazione intenzionale per evitare allocazioni eccessive quando si utilizza la sottostringa in scenari comuni. Sfortunatamente, il codice problematico colpisce un caso in cui il sovraccarico dell'array originale è evidente. È difficile da ottimizzare per entrambi i casi di bordi. Qualsiasi ottimizzazione per i compromessi spazio/dimensione è generalmente complessa e può spesso essere specifica per piattaforma.

C'è anche questo note, notando che quello che una volta era un'ottimizzazione era diventato un pessimization secondo i test:

Per un lungo preparati di tempo e piallatura sono in corso per rimuovere l'offset e contare i campi da java.lang.String. Questi due campi consentono a più istanze di String di condividere lo stesso buffer di caratteri di supporto. I buffer di caratteri condivisi erano un'importante ottimizzazione per i vecchi benchmark, ma con l'attuale codice e benchmark del mondo reale è meglio non condividere i buffer di backup. I buffer di supporto di array di char condivisi si limitano a "vincere" con l'uso molto intenso di String.substring. Le situazioni con impatto negativo possono includere parser e compilatori, tuttavia gli attuali test dimostrano che nel complesso questo cambiamento è vantaggioso.

8

Se si dispone di una sottostringa piccola di lunga durata di una stringa padre di breve durata e di grandi dimensioni, il grande char [] backing della stringa padre non sarà idoneo per la garbage collection finché la sottostringa piccola non viene spostata. Ciò significa che una sottostringa può occupare molta più memoria di quanto ci si aspetti.

L'unica volta che il modo Java 6 ha ottenuto risultati significativamente migliori è stato quando qualcuno ha utilizzato una sottostringa di grandi dimensioni da una stringa padre di grandi dimensioni, il che è un caso molto raro.

Chiaramente hanno deciso che il piccolo costo delle prestazioni di questo cambiamento era superato dai problemi di memoria nascosti causati dal vecchio modo. Il fattore determinante è che il problema è stato nascosto, non che c'è una soluzione alternativa.

+0

trim() prende una sottostringa grande da una stringa padre di grandi dimensioni e viene utilizzata tutto il tempo. – Don

+0

L'incontro con le prestazioni danneggiate agli algoritmi a causa di questa (scarsa) decisione di progettazione è un evento comune, non raro. – javadba

5

Ciò influirà sulla complessità delle strutture dati come gli array di suffissi con un margine equo. Java dovrebbe fornire un metodo alternativo per ottenere una parte della stringa originale.

3

È solo il loro modo scadente di correggere alcuni limiti della raccolta di immondizie JVM.

Prima di Java 7, se si vuole evitare il problema della garbage collection, si può sempre copiare la sottostringa invece di mantenere il riferimento subString. Era solo una chiamata in più per il costruttore di copia:

String smallStr = new String(largeStr.substring(0,2)); 

Ma ora, non si può più avere un tempo sottoStringa costante. Che disastro.

+0

Questo è completamente vero. Sono molti i tipi di programmi che hanno beneficiato dell'uso della sottostringa condivisa. I compilatori e i parser sono una buona illustrazione del * tipo * delle operazioni che fanno più male: ma il danno si estende ben oltre quei tipi specifici di programmi. – javadba

+0

Qualcuno è a conoscenza di una lib/codice di terze parti con un'implementazione personalizzata di CharSequence (o qualcosa di simile) che replica il "vecchio" comportamento della sottostringa? Spesso devo elaborare grandi file simili a CSV (500 + MB) e ogni volta che li registro mi rendo conto che almeno il 10% del tempo di elaborazione sembra essere sprecato nelle chiamate a Arrays.copyOfRange(). –

1

La motivazione principale, credo, è l'eventuale "co-locazione" di String e il suo char[].In questo momento si localizzano a distanza, che è una penalità maggiore sulle linee della cache. Se ogni String possiede il suo char[], JVM può unirli insieme e la lettura sarà molto più veloce.