Quelle est la manière la plus efficace de parcourir une collection?
List<Integer> a = new ArrayList<Integer>();
for (Integer integer : a) {
integer.toString();
}
ou
List<Integer> a = new ArrayList<Integer>();
for (Iterator iterator = a.iterator(); iterator.hasNext();) {
Integer integer = (Integer) iterator.next();
integer.toString();
}
Veuillez noter que ce n'est pas un double exact de ceci , ceci , ceci ou ceci , bien qu'une des réponses à la dernière question soit proche. La raison pour laquelle ce n'est pas dupe, c'est que la plupart d'entre elles comparent des boucles où vous appelez get(i)
à l'intérieur de la boucle, plutôt que d'utiliser l'itérateur.
Comme suggéré sur Meta, je posterai ma réponse à cette question.
java
collections
foreach
Paul Wagland
la source
la source
Réponses:
Si vous vous promenez dans la collection pour lire toutes les valeurs, il n'y a aucune différence entre l'utilisation d'un itérateur ou la nouvelle syntaxe de boucle for, car la nouvelle syntaxe utilise simplement l'itérateur sous l'eau.
Si toutefois, vous entendez par boucle l'ancienne boucle "de style c":
La nouvelle boucle for, ou itérateur, peut alors être beaucoup plus efficace, selon la structure de données sous-jacente. La raison en est que pour certaines structures de données, il
get(i)
s'agit d'une opération O (n), qui fait de la boucle une opération O (n 2 ). Une liste chaînée traditionnelle est un exemple d'une telle structure de données. Tous les itérateurs ont comme exigence fondamentale quinext()
devrait être une opération O (1), ce qui rend la boucle O (n).Pour vérifier que l'itérateur est utilisé sous l'eau par la nouvelle syntaxe de boucle for, comparez les bytecodes générés à partir des deux extraits de code Java suivants. D'abord la boucle for:
Et deuxièmement, l'itérateur:
Comme vous pouvez le voir, le code d'octet généré est effectivement identique, il n'y a donc aucune pénalité de performance à utiliser l'un ou l'autre formulaire. Par conséquent, vous devez choisir la forme de boucle la plus attrayante pour vous, pour la plupart des personnes qui seront la boucle pour chaque, car elle a moins de code passe-partout.
la source
for(int i; i < list.size(); i++) {
boucle de style doit également être évaluéelist.size()
à la fin de chaque itération, si elle est utilisée, elle est parfois plus efficace pour mettre en cache le résultat de lalist.size()
première.La différence n'est pas dans les performances, mais dans les capacités. Lorsque vous utilisez directement une référence, vous avez plus de pouvoir sur l'utilisation explicite d'un type d'itérateur (par exemple, List.iterator () par rapport à List.listIterator (), bien que dans la plupart des cas, ils renvoient la même implémentation). Vous avez également la possibilité de référencer l'itérateur dans votre boucle. Cela vous permet de supprimer des éléments de votre collection sans obtenir une exception ConcurrentModificationException.
par exemple
C'est acceptable:
Ce n'est pas le cas, car cela lèvera une exception de modification simultanée:
la source
Pour développer la réponse de Paul, il a démontré que le bytecode est le même sur ce compilateur particulier (vraisemblablement le javac de Sun?) Mais différents compilateurs ne sont pas garantis pour générer le même bytecode, non? Pour voir quelle est la différence réelle entre les deux, allons directement à la source et vérifions les spécifications du langage Java, en particulier 14.14.2, "La déclaration améliorée pour" :
En d'autres termes, le JLS exige que les deux soient équivalents. En théorie, cela pourrait signifier des différences marginales dans le bytecode, mais en réalité, la boucle for améliorée est nécessaire pour:
.iterator()
méthode.hasNext()
.next()
En d'autres termes, à toutes fins pratiques, le bytecode sera identique ou presque identique. Il est difficile d'envisager une implémentation de compilateur qui entraînerait une différence significative entre les deux.
la source
Le
foreach
underhood crée leiterator
, appelant hasNext () et appelant next () pour obtenir la valeur; Le problème avec les performances ne survient que si vous utilisez quelque chose qui implémente RandomomAccess.Les problèmes de performances avec la boucle basée sur l'itérateur sont dus au fait que:
Iterator<CustomObj> iter = customList.iterator();
);iter.hasNext()
à chaque itération de la boucle, il y a un appel virtuel invokeInterface (parcourez toutes les classes, puis recherchez la table des méthodes avant le saut).hasNext()
la valeur de l'appel soit la valeur: # 1 get current count et # 2 get total countiter.next
(donc: parcourez toutes les classes et effectuez la recherche de table de méthode avant le saut) et doit également faire la recherche de champs: # 1 obtenir l'index et # 2 obtenir la référence à la tableau pour y faire l'offset (à chaque itération).Une optimisation potentielle consiste à passer à un
index iteration
avec la recherche de taille en cache:Ici nous avons:
customList.size()
lors de la création initiale de la boucle for pour obtenir la taillecustomList.get(x)
pendant le corps de la boucle, qui est une recherche de champ dans le tableau et peut ensuite effectuer le décalage dans le tableauNous avons réduit une tonne d'appels de méthode, de recherches sur le terrain. Cela vous ne voulez pas faire avec
LinkedList
ou avec quelque chose qui n'est pas unRandomAccess
obj de collection, sinon celacustomList.get(x)
va se transformer en quelque chose qui doit traverser leLinkedList
à chaque itération.C'est parfait lorsque vous savez qu'il s'agit d'une
RandomAccess
collection de listes basée.la source
foreach
utilise de toute façon des itérateurs sous le capot. Ce n'est vraiment que du sucre syntaxique.Considérez le programme suivant:
Compilons-le avec
javac Whatever.java
,Et lisons le bytecode démonté de
main()
, en utilisantjavap -c Whatever
:Nous pouvons voir que se
foreach
compile en un programme qui:List.iterator()
Iterator.hasNext()
: invoqueIterator.next()
et continue la boucleQuant à "pourquoi cette boucle inutile n'est-elle pas optimisée à partir du code compilé? Nous pouvons voir qu'elle ne fait rien avec l'élément de liste": eh bien, il vous est possible de coder votre itérable de manière à
.iterator()
avoir des effets secondaires , ou alors qui.hasNext()
a des effets secondaires ou des conséquences significatives.Vous pourriez facilement imaginer qu'un itérable représentant une requête déroulante à partir d'une base de données pourrait faire quelque chose de dramatique
.hasNext()
(comme contacter la base de données ou fermer un curseur parce que vous avez atteint la fin de l'ensemble de résultats).Donc, même si nous pouvons prouver que rien ne se passe dans le corps de la boucle… il est plus coûteux (insoluble?) De prouver que rien de significatif / de conséquent ne se produit lorsque nous itérons. Le compilateur doit laisser ce corps de boucle vide dans le programme.
Le mieux que nous puissions espérer serait un avertissement du compilateur . Il est intéressant que
javac -Xlint:all Whatever.java
ne pas nous mettre en garde contre ce corps de la boucle vide. IntelliJ IDEA le fait cependant. Certes, j'ai configuré IntelliJ pour utiliser Eclipse Compiler, mais ce n'est peut-être pas la raison.la source
Iterator est une interface dans le cadre des collections Java qui fournit des méthodes pour parcourir ou parcourir une collection.
L'itérateur et la boucle for agissent de manière similaire lorsque votre motif est de simplement parcourir une collection pour lire ses éléments.
for-each
n'est qu'une façon de parcourir la Collection.Par exemple:
Et la boucle for-each ne peut être utilisée que sur des objets implémentant l'interface de l'itérateur.
Revenons maintenant au cas de la boucle for et de l'itérateur.
La différence survient lorsque vous essayez de modifier une collection. Dans ce cas, l'itérateur est plus efficace en raison de sa propriété fail-fast . c'est à dire. il vérifie toute modification dans la structure de la collection sous-jacente avant d'itérer sur l'élément suivant. Si des modifications sont trouvées, il lèvera l' exception ConcurrentModificationException .
(Remarque: Cette fonctionnalité de l'itérateur n'est applicable que dans le cas des classes de collection dans le package java.util. Elle n'est pas applicable aux collections simultanées car elles sont de sécurité intrinsèque par nature)
la source
Nous devons éviter d'utiliser la boucle for traditionnelle lors de l'utilisation des collections. La raison simple que je donnerai est que la complexité de la boucle for est de l'ordre O (sqr (n)) et la complexité de l'itérateur ou même la boucle for améliorée est juste O (n). Cela donne donc une différence de performance. Prenez simplement une liste de quelque 1000 articles et imprimez-la dans les deux sens. et également imprimer la différence de temps pour l'exécution. Vous pouvez voir la différence.
la source