web-dev-qa-db-fra.com

Pourquoi démarrer une ArrayList avec une capacité initiale?

Le constructeur habituel de ArrayList est:

ArrayList<?> list = new ArrayList<>();

Mais il existe également un constructeur surchargé avec un paramètre pour sa capacité initiale:

ArrayList<?> list = new ArrayList<>(20);

Pourquoi est-il utile de créer une ArrayList avec une capacité initiale lorsque nous pouvons l’ajouter à notre guise?

146
Rob

Si vous savez à l'avance quelle sera la taille de la ArrayList, il est plus efficace de spécifier la capacité initiale. Si vous ne le faites pas, le tableau interne devra être réaffecté à plusieurs reprises à mesure que la liste s'allonge.

Plus la liste finale est longue, plus vous gagnez du temps en évitant les réaffectations.

Cela dit, même sans pré-affectation, il est garanti que n insérer des éléments à l’arrière d’un ArrayList prend le temps total O(n). En d'autres termes, l'ajout d'un élément est une opération à temps constant et amortie. Pour ce faire, chaque réallocation augmente de façon exponentielle la taille du tableau, généralement d'un facteur 1.5. Avec cette approche, le nombre total d'opérations on peut montrer que O(n) .

193
NPE

Parce que ArrayList est une structure de données tableau à redimensionnement dynamique , ce qui signifie qu'elle est implémentée en tant que tableau avec une taille fixe initiale (par défaut). Lorsque cela est rempli, le tableau sera étendu à un double taille. Cette opération est coûteuse, vous voulez donc le moins possible.

Donc, si vous savez que votre limite supérieure est de 20 éléments, il est préférable de créer un tableau de longueur initiale de 20 plutôt que d'utiliser un défaut de, disons 15, puis de le redimensionner à 15*2 = 30 et de n'utiliser que 20 tout en perdant les cycles. pour l'expansion.

P.S. - Comme AmitG le dit, le facteur d'expansion est spécifique à la mise en oeuvre (dans ce cas (oldCapacity * 3)/2 + 1)

41
Iulius Curt

La taille par défaut de Arraylist est 1.

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
    this(10);
    } 

Ainsi, si vous allez ajouter 100 enregistrements ou plus, vous pouvez voir le surcoût de la réallocation de mémoire.

ArrayList<?> list = new ArrayList<>();    
// same as  new ArrayList<>(10);      

Donc, si vous avez une idée du nombre d’éléments qui seront stockés dans Arraylist, il est préférable de créer Arraylist avec cette taille au lieu de commencer par 10 puis de l’augmenter.

25
xyz

En fait, j'ai écrit un blog post sur le sujet il y a 2 mois. Cet article concerne les List<T> de C #, mais les ArrayList de Java ont une implémentation très similaire. Comme ArrayList est implémenté à l'aide d'un tableau dynamique, sa taille augmente à la demande. Le constructeur de capacité est donc utilisé à des fins d'optimisation.

Lorsque l'une de ces opérations de redimensionnement est effectuée, ArrayList copie le contenu du tableau dans un nouveau tableau qui correspond à deux fois la capacité de l'ancien. Cette opération s'exécute dans le temps O (n) .

Exemple

Voici un exemple de la façon dont la taille de ArrayList augmenterait:

10
16
25
38
58
... 17 resizes ...
198578
297868
446803
670205
1005308

La liste commence donc par une capacité de 10, lorsque le 11ème élément est ajouté, il est augmenté de 50% + 1 à 16. Sur le 17ème élément, la ArrayList est augmentée à 25 et ainsi de suite. Prenons maintenant l'exemple où nous créons une liste dans laquelle la capacité souhaitée est déjà connue sous le nom de 1000000. La création de ArrayList sans le constructeur de taille appelle ArrayList.add1000000 fois qui prend O (1) normalement ou O (n) en redimensionnement.

1000000 + 16 + 25 + ... + 670205 + 1005308 = 4015851 opérations

Comparez ceci en utilisant le constructeur puis en appelant ArrayList.add dont l'exécution est garantie O (1) .

1000000 + 1000000 = 2000000 opérations

Java vs C #

Java est comme ci-dessus, commençant à 10 et augmentant chaque redimensionnement à 50% + 1. C # commence à 4 et augmente beaucoup plus agressivement, en doublant à chaque redimensionnement. Le 1000000 ajoute un exemple de ci-dessus pour C # utilise les opérations 3097084.

Références

16
Daniel Imms

Définition de la taille initiale d'une ArrayList, par ex. to ArrayList<>(100), réduit le nombre de réallocations de mémoire interne.

Exemple:

ArrayList example = new ArrayList<Integer>(3);
example.add(1); // size() == 1
example.add(2); // size() == 2, 
example.add(2); // size() == 3, example has been 'filled'
example.add(3); // size() == 4, example has been 'expanded' so that the fourth element can be added. 

Comme vous le voyez dans l'exemple ci-dessus, un ArrayList peut être développé si nécessaire. Ce que cela ne vous montre pas, c'est que la taille de l'Arraylist double généralement (bien que la nouvelle taille dépende de votre implémentation). Ce qui suit est cité de Oracle :

"Chaque instance de ArrayList a une capacité. La capacité est la taille du tableau utilisé pour stocker les éléments dans la liste. Elle est toujours au moins aussi grande que la taille de la liste. Lorsque des éléments sont ajoutés à un ArrayList, sa capacité augmente automatiquement. Les détails de la politique de croissance ne sont pas spécifiés au-delà du fait que l'ajout d'un élément a un coût en temps amorti constant. "

Évidemment, si vous n'avez aucune idée du type de plage que vous allez gérer, il ne sera probablement pas judicieux de définir la taille. Toutefois, si vous avez une plage spécifique en tête, définir une capacité initiale augmentera l'efficacité de la mémoire. .

8
lifetimes

Cela évite des efforts possibles de réaffectation de chaque objet.

int newCapacity = (oldCapacity * 3)/2 + 1;

en interne new Object[] est créé.
JVM a besoin d'efforts pour créer new Object[] lorsque vous ajoutez un élément à la liste. Si vous n'avez pas le code ci-dessus (aucun algo que vous pensez) pour la réallocation, alors chaque fois que vous appelez arraylist.add(), alors new Object[] doit être créé, ce qui est inutile et nous perdons du temps pour augmenter la taille de 1 pour chaque objet à ajouter. Il est donc préférable d’augmenter la taille de Object[] avec la formule suivante.
(JSL a utilisé la formule de prédiction indiquée ci-dessous pour les listes à croissance dynamique au lieu de 1 à chaque fois. Parce que pour le faire fructifier, la JVM fait un effort)

int newCapacity = (oldCapacity * 3)/2 + 1;
3
AmitG

ArrayList peut contenir de nombreuses valeurs et lorsque vous effectuez des insertions initiales volumineuses, vous pouvez indiquer à ArrayList d'allouer un stockage plus volumineux afin de ne pas gaspiller les cycles du processeur lorsqu'il essaie d'allouer plus d'espace au prochain élément. Il est donc plus efficace d’allouer de l’espace au début.

3
Sanober Malik

Je pense que chaque ArrayList est créé avec une valeur de capacité init de "10". Donc de toute façon, si vous créez un ArrayList sans définir de capacité dans le constructeur, il sera créé avec une valeur par défaut.

2
sk2212

Je dirais que c'est une optimisation. ArrayList sans capacité initiale aura environ 10 lignes vides et se développera lorsque vous ferez un ajout.

Pour avoir une liste contenant exactement le nombre d’éléments à appeler trimToSize ()

2
Daniel Magnusson

D'après mon expérience avec ArrayList, attribuer une capacité initiale est un moyen agréable d'éviter les coûts de réaffectation. Mais cela mérite une mise en garde. Toutes les suggestions mentionnées ci-dessus indiquent qu'il ne faut fournir une capacité initiale que lorsqu'une estimation approximative du nombre d'éléments est connue. Mais lorsque nous essayons de donner une capacité initiale sans aucune idée, la quantité de mémoire réservée et inutilisée sera un gaspillage, car elle ne sera peut-être plus nécessaire une fois que la liste sera remplie avec le nombre d'éléments requis. Ce que je dis, c'est que nous pouvons être pragmatiques au début tout en allouant de la capacité, puis trouver un moyen intelligent de connaître la capacité minimale requise au moment de l'exécution. ArrayList fournit une méthode appelée ensureCapacity(int minCapacity). Mais alors, on doit trouver un moyen intelligent ...

0
Tushar Patidar

J'ai testé ArrayList avec et sans initialCapacity et j'ai obtenu un résultat surprenant
Lorsque je règle LOOP_NUMBER à 100 000 ou moins, le résultat est que le paramètre initialCapacity est efficace.

list1Sttop-list1Start = 14
list2Sttop-list2Start = 10


Mais lorsque je règle LOOP_NUMBER sur 1 000 000, le résultat devient:

list1Stop-list1Start = 40
list2Stop-list2Start = 66


Enfin, je ne pouvais pas comprendre comment ça marche?!
Exemple de code:

 public static final int LOOP_NUMBER = 100000;

public static void main(String[] args) {

    long list1Start = System.currentTimeMillis();
    List<Integer> list1 = new ArrayList();
    for (int i = 0; i < LOOP_NUMBER; i++) {
        list1.add(i);
    }
    long list1Stop = System.currentTimeMillis();
    System.out.println("list1Stop-list1Start = " + String.valueOf(list1Stop - list1Start));

    long list2Start = System.currentTimeMillis();
    List<Integer> list2 = new ArrayList(LOOP_NUMBER);
    for (int i = 0; i < LOOP_NUMBER; i++) {
        list2.add(i);
    }
    long list2Stop = System.currentTimeMillis();
    System.out.println("list2Stop-list2Start = " + String.valueOf(list2Stop - list2Start));
}

J'ai testé sur windows8.1 et jdk1.7.0_80

0
Hamedz