In Java 8, perché la capacità predefinita di ArrayList ora è zero?

Come ricordo, prima di Java 8, la capacità predefinita di ArrayList era 10.

Sorprendentemente, il commento sul costruttore predefinito (void) dice ancora: Constructs an empty list with an initial capacity of ten.

Da ArrayList.java :

 /** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; ... /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } 

Tecnicamente, è 10 , non zero, se ammetti per un’inizializzazione pigra dell’array di supporto. Vedere:

 public boolean add(E e) { ensureCapacityInternal(size + 1); elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } 

dove

 /** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10; 

Quello a cui ti riferisci è solo l’object array iniziale di dimensioni zero che è condiviso tra tutti gli oggetti ArrayList inizialmente vuoti. Cioè la capacità di 10 è garantita pigramente , un’ottimizzazione che è presente anche in Java 7.

Certo, il contratto di costruzione non è del tutto preciso. Forse questa è la fonte di confusione qui.

sfondo

Ecco un e-mail di Mike Duigou

Ho pubblicato una versione aggiornata della patch ArrayList e HashMap vuota.

http://cr.openjdk.java.net/~mduigou/JDK-7143928/1/webrev/

Questa implementazione rivista non introduce nuovi campi per nessuna delle due classi. Per ArrayList, l’allocazione lenta dell’array di backup si verifica solo se l’elenco viene creato con le dimensioni predefinite. Secondo il nostro team di analisi delle prestazioni, circa l’85% delle istanze di ArrayList viene creato con dimensioni predefinite, quindi questa ottimizzazione sarà valida per la stragrande maggioranza dei casi.

Per HashMap, viene fatto un uso creativo del campo soglia per tracciare la dimensione iniziale richiesta fino a quando non è necessario l’array bucket. Sul lato di lettura il caso della mappa vuota viene testato con isEmpty (). Nella dimensione di scrittura viene utilizzato un confronto di (tabella == EMPTY_TABLE) per rilevare la necessità di gonfiare l’array di bucket. In readObject c’è un po ‘più di lavoro per cercare di scegliere una capacità iniziale efficiente.

Da: http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-April/015585.html

In Java 8 la capacità predefinita di ArrayList è 0 fino a quando non aggiungiamo almeno un object nell’object ArrayList (è ansible chiamarlo inizializzazione pigro). Si prega di vedere sotto il codice per aiuto.

 ArrayList al = new ArrayList(); //Size: 0, Capacity: 0 ArrayList al = new ArrayList(5); //Size: 0, Capacity: 5 ArrayList al = new ArrayList(new ArrayList(5)); //Size: 0, Capacity: 0 al.add( "shailesh" ); //Size: 1, Capacity: 10 public static void main( String[] args ) throws Exception { ArrayList al = new ArrayList(); getCapacity( al ); al.add( "shailesh" ); getCapacity( al ); } static void getCapacity( ArrayList l ) throws Exception { Field dataField = ArrayList.class.getDeclaredField( "elementData" ); dataField.setAccessible( true ); System.out.format( "Size: %2d, Capacity: %2d%n", l.size(), ( (Object[]) dataField.get( l ) ).length ); } Response: - Size: 0, Capacity: 0 Size: 1, Capacity: 10 

Ora la domanda è: perché questo cambiamento è stato fatto in JAVA 8?

La risposta è di risparmiare sul consumo di memoria. Milioni di elenchi di array vengono creati in applicazioni java in tempo reale. La dimensione predefinita di 10 oggetti significa che allociamo 10 puntatori (40 o 80 byte) per l’array sottostante alla creazione e li riempiamo con valori null. Un array vuoto (pieno di null) occupa molta memoria.

L’inizializzazione pigra rimanda questo consumo di memoria fino al momento in cui verrà effettivamente utilizzato l’elenco di array.

Articolo La capacità di default di ArrayList in Java 8 lo spiega nei dettagli.

Se la prima operazione che viene eseguita con un ArrayList è di passare addAll una raccolta che ha più di dieci elementi, quindi qualsiasi sforzo messo nella creazione di una matrice iniziale di dieci elementi per contenere i contenuti dell’ArrayList verrà buttato fuori dalla finestra. Ogni volta che qualcosa viene aggiunto a un ArrayList è necessario verificare se la dimensione dell’elenco risultante supera la dimensione del backing store; consentendo all’archivio di backup iniziale di avere una dimensione zero anziché dieci, questo test avrà esito negativo una volta nel corso della durata di un elenco la cui prima operazione è un “add” che richiederebbe la creazione dell’array iniziale di dieci elementi, ma tale costo è meno del costo di creare un array di dieci elementi che non finisce mai per essere utilizzato.

Detto questo, sarebbe stato ansible migliorare ulteriormente le prestazioni in alcuni contesti se ci fosse un sovraccarico di “addAll” che specificasse quanti articoli (se ce ne fossero) verrebbero probabilmente aggiunti alla lista dopo quella attuale, e quali potrebbero utilizzalo per influenzare il suo comportamento di allocazione. In alcuni casi il codice che aggiunge gli ultimi elementi a una lista avrà una buona idea che l’elenco non avrà mai bisogno di spazio oltre. Ci sono molte situazioni in cui una lista verrà popolata una volta e non verrà mai modificata successivamente. Se al punto di codice sappiamo che la dimensione massima di una lista sarà di 170 elementi, ha 150 elementi e un backing store di dimensione 160, la crescita del backing store nella dimensione 320 sarà inutile e lasciandola alla dimensione 320 o tagliandola a 170 sarà meno efficiente del semplice aumento della quota successiva a 170.

La domanda è “perché?”.

Ispezioni sulla profilazione della memoria (ad esempio ( https://www.yourkit.com/docs/java/help/inspections_mem.jsp#sparse_arrays ) mostrano che gli array vuoti (pieni di null) occupano tonnellate di memoria.

La dimensione predefinita di 10 oggetti significa che allociamo 10 puntatori (40 o 80 byte) per l’array sottostante alla creazione e li riempiamo con valori null. Le applicazioni Java reali creano milioni di elenchi di array.

La modifica introdotta rimuove ^ W rimanda questo consumo di memoria fino al momento in cui utilizzerai effettivamente l’elenco di array.

La dimensione predefinita di ArrayList in JAVA 8 è di tipo 10. L’unica modifica apportata in JAVA 8 è che se un codificatore aggiunge elementi inferiori a 10, i posti vuoti dell’arrayylist rimanenti non vengono specificati come null. Dicendo così perché sono passato attraverso questa situazione ed eclipse mi ha fatto guardare in questo cambiamento di JAVA 8.

Puoi giustificare questo cambiamento osservando lo screenshot qui sotto. In esso si può vedere che la dimensione di ArrayList è specificata come 10 in Object [10] ma il numero di elementi visualizzati è solo 7. Gli elementi di valore null di rest non vengono visualizzati qui. In JAVA 7 qui sotto lo screenshot è lo stesso con una singola modifica, ovvero che vengono visualizzati gli elementi del valore nullo per i quali il codificatore deve scrivere il codice per gestire i valori nulli se sta iterando l’elenco completo degli array mentre in JAVA 8 questo carico viene rimosso da il capo del codificatore / sviluppatore.

Collegamento a schermo.

Dopo la domanda precedente ho esaminato ArrayList Document di Java 8. Ho trovato che la dimensione predefinita è ancora solo 10.

Vedi sotto