Dans Java 8, pourquoi la capacité par défaut de ArrayList est-elle désormais nulle?

Si je me souviens bien, avant Java 8, la capacité par défaut d’ ArrayList était de 10.

Étonnamment, le commentaire sur le constructeur par défaut (void) dit toujours: Constructs an empty list with an initial capacity of ten.

De 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; } 

Techniquement, c’est 10 , pas zéro, si vous admettez une initialisation paresseuse du tableau de sauvegarde. Voir:

 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); } 

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

Vous faites simplement référence à l’object de tableau initial de taille zéro partagé entre tous les objects ArrayList initialement vides. Ie la capacité de 10 est garantie paresseusement , une optimisation qui est également présente dans Java 7.

Certes, le contrat de constructeur n’est pas tout à fait exact. C’est peut-être la source de confusion ici.

Contexte

Voici un courriel de Mike Duigou

J’ai publié une version mise à jour du patch vide ArrayList et HashMap.

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

Cette implémentation révisée n’introduit aucun nouveau champ dans les deux classes. Pour ArrayList, l’allocation différée du tableau de sauvegarde ne se produit que si la liste est créée à la taille par défaut. Selon notre équipe d’parsing des performances, environ 85% des instances ArrayList sont créées à la taille par défaut. Cette optimisation sera donc valide pour la grande majorité des cas.

Pour HashMap, le champ de seuil est utilisé de manière créative pour suivre la taille initiale requirejse jusqu’à ce que la baie de compartiments soit requirejse. Du côté de la lecture, le casse-carte vide est testé avec isEmpty (). Sur la taille d’écriture, une comparaison de (table == EMPTY_TABLE) est utilisée pour détecter la nécessité de gonfler le tableau de baquets. Dans readObject, il y a un peu plus de travail pour essayer de choisir une capacité initiale efficace.

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

Dans java 8, la capacité par défaut de ArrayList est 0 jusqu’à ce que nous ajoutions au moins un object dans l’object ArrayList (vous pouvez l’appeler initialisation paresseuse). Veuillez consulter le code ci-dessous pour obtenir de l’aide.

 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( Ssortingng[] 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 

Maintenant la question est pourquoi ce changement a été fait dans JAVA 8?

La réponse est d’économiser la consommation de mémoire. Des millions d’objects de liste de tableaux sont créés dans des applications Java en temps réel. La taille par défaut de 10 objects signifie que nous allouons 10 pointeurs (40 ou 80 octets) pour le tableau sous-jacent à la création et les remplissons avec des valeurs NULL. Un tableau vide (rempli de null) occupe beaucoup de mémoire.

L’initialisation paresseuse reporte cette consommation de mémoire jusqu’au moment où vous utiliserez la liste de tableaux.

Article La capacité par défaut de ArrayList dans Java 8 l’ explique en détail.

Si la toute première opération effectuée avec un ArrayList consiste à transmettre addAll une collection addAll plus de dix éléments, toute tentative de création d’un tableau initial à dix éléments pour contenir le contenu de ArrayList serait rejetée par la fenêtre. Chaque fois que quelque chose est ajouté à une ArrayList, il est nécessaire de tester si la taille de la liste résultante dépasse la taille de la banque de sauvegarde. autoriser le magasin de sauvegarde initial à avoir une taille de zéro plutôt que de dix entraînera l’échec de ce test pendant la durée de vie d’une liste dont la première opération est un “ajout” nécessitant la création du tableau initial à dix éléments, mais ce coût est moins que le coût de création d’un tableau de dix éléments qui ne finit jamais par être utilisé.

Cela étant dit, il aurait été possible d’améliorer davantage les performances dans certains contextes s’il y avait une surcharge de “addAll” qui spécifiait combien d’éléments (le cas échéant) seraient probablement ajoutés à la liste après celui-ci, et qui pourrait utiliser cela pour influencer son comportement d’allocation. Dans certains cas, le code qui ajoute les derniers éléments à une liste aura une très bonne idée que la liste n’aura jamais besoin d’espace supplémentaire. Il y a beaucoup de situations où une liste sera remplie une fois et jamais modifiée par la suite. Si le code de point sait que la taille finale d’une liste sera de 170 éléments, il aura 150 éléments et un magasin de sauvegarde de taille 160, ce qui rendra le magasin de sauvegarde de taille 320 inutile. 170 sera moins efficace que la simple augmentation de la prochaine allocation à 170.

La question est «pourquoi?

Les inspections de profilage de la mémoire (par exemple ( https://www.yourkit.com/docs/java/help/inspections_mem.jsp#sparse_arrays ) montrent que les tableaux vides (remplis de valeurs null) occupent des tonnes de mémoire.

La taille par défaut de 10 objects signifie que nous allouons 10 pointeurs (40 ou 80 octets) pour le tableau sous-jacent à la création et les remplissons avec des valeurs NULL. Les applications Java réelles créent des millions de listes de tableaux.

La modification introduite supprime ^ W reporter cette consommation de mémoire jusqu’au moment où vous utiliserez réellement la liste de tableaux.

La taille par défaut de ArrayList dans JAVA 8 est stil 10. Le seul changement apporté à JAVA 8 est que si un codeur ajoute des éléments inférieurs à 10, les espaces vides restants de l’arraylist ne sont pas spécifiés à null. Le dire parce que je suis moi-même passé par cette situation et que l’éclipse m’a fait regarder ce changement de JAVA 8.

Vous pouvez justifier ce changement en regardant la capture d’écran ci-dessous. Vous pouvez voir que la taille de ArrayList est spécifiée comme 10 dans Object [10], mais que le nombre d’éléments affichés n’est que de 7. Les éléments de valeur null restants ne sont pas affichés ici. Dans JAVA 7 ci-dessous, la capture d’écran est identique à un seul changement, à savoir que les éléments à valeur nulle sont également affichés pour lesquels le codeur doit écrire du code pour gérer les valeurs NULL s’il itère la liste complète. le responsable du codeur / développeur.

Lien de capture d’écran.

Après la question ci-dessus, j’ai parcouru le document ArrayList de Java 8. J’ai trouvé que la taille par défaut est toujours 10 seulement.

S'il vous plaît voir ci-dessous