web-dev-qa-db-fra.com

Performance du traditionnel pour la boucle vs Iterator/foreach en Java

Existe-t-il des résultats de test de performance permettant de comparer les résultats traditionnels pour les boucles par rapport à Iterator tout en parcourant une collection ArrayList, HashMap et d'autres?

Ou simplement pourquoi devrais-je utiliser Iterator sur for loop ou vice versa?

58
Harish

En supposant que c'est ce que vous vouliez dire:

// traditional for loop
for (int i = 0; i < collection.size(); i++) {
  T obj = collection.get(i);
  // snip
}

// using iterator
Iterator<T> iter = collection.iterator();
while (iter.hasNext()) {
  T obj = iter.next();
  // snip
}

// using iterator internally (confirm it yourself using javap -c)
for (T obj : collection) {
   // snip
}

L'itérateur est plus rapide pour les collections sans accès aléatoire (TreeSet, HashMap, LinkedList, par exemple). Pour les tableaux et les tableaux, les différences de performances doivent être négligeables. 

Edit: Je crois que le micro-benchmarking est à la base de presque tout le mal, tout comme l'optimisation précoce. Mais encore une fois, je pense qu’il est bon d’avoir une idée des conséquences de choses aussi triviales. J'ai donc exécuté un petit test

  • itérer sur une LinkedList et une ArrayList respectivement
  • avec 100 000 chaînes "aléatoires"
  • résumant leur longueur (juste quelque chose pour éviter que le compilateur optimise toute la boucle)
  • en utilisant les 3 styles de boucle (itérateur, pour chacun, pour avec compteur)

Les résultats sont similaires pour tous sauf "pour avec compteur" avec LinkedList. Tous les cinq autres ont mis moins de 20 millisecondes à parcourir la liste entière. Utiliser list.get(i) sur une LinkedList 100 000 fois a pris plus de 2 minutes (!) (60 000 fois plus lent). Hou la la! :) Il est donc préférable d'utiliser un itérateur (explicitement ou implicitement pour chacun), surtout si vous ne connaissez pas le type et la taille de la liste avec laquelle vous traitez. 

79
sfussenegger

La première raison d'utiliser un itérateur est une exactitude évidente. Si vous utilisez un index manuel, il peut y avoir des erreurs off-by-one très anodines que vous ne pouvez voir que si vous regardez de très près: avez-vous commencé à 1 ou à 0? Avez-vous fini à length - 1? Avez-vous utilisé < ou <=? Si vous utilisez un itérateur, il est beaucoup plus facile de voir qu'il s'agit en réalité d'une itération de l'ensemble du tableau. "Dis ce que tu fais, fais ce que tu dis."

La deuxième raison est un accès uniforme à différentes structures de données. Un tableau est accessible efficacement via un index, mais il est préférable de parcourir une liste chaînée en gardant en mémoire le dernier élément utilisé (sinon, vous obtenez un " Shlemiel le peintre "). Un hashmap est encore plus compliqué. En fournissant une interface uniforme à partir de ces structures de données et d'autres (par exemple, vous pouvez également effectuer des traversées d'arbres), vous obtenez à nouveau une exactitude évidente. La logique de parcours ne doit être implémentée qu'une seule fois, et le code qui l'utilise peut concement "dire ce qu'il fait et faire ce qu'il dit".

22
Svante

Les performances sont similaires dans la plupart des cas.

Cependant, chaque fois qu'un code reçoit une liste et y boucle, il existe un cas bien connu:
Iterator est bien meilleur pour toutes les implémentations de liste qui n'implémentent pas RandomAccess (exemple: LinkedList).

La raison en est que pour ces listes, l'accès à un élément par index n'est pas une opération à temps constant.

Ainsi, vous pouvez également considérer l'Iterator comme plus robuste (aux détails d'implémentation).


Comme toujours, les performances ne doivent pas être masquées par des problèmes de lisibilité.
La boucle Java5 foreach est un succès dans cet aspect :-)

4
KLE

Je n'y crois pas

for (T obj : collection) {

calcule .size () à chaque fois dans la boucle et est donc plus rapide que

for (int i = 0; i < collection.size(); i++) {
2
MeBigFatGuy

Oui, cela fait une différence pour les collections qui ne sont pas basées sur un accès aléatoire comme LinkedList. Une liste liée en interne est implémentée par les nœuds pointant vers le prochain (en partant d'un nœud principal). 

La méthode get (i) dans une liste liée commence à partir du nœud principal et navigue à travers les liens jusqu'au n ième nœud. Lorsque vous parcourez la liste liée en utilisant une boucle for traditionnelle, vous redémarrez à partir du nœud principal à chaque fois. La traversée globale devient alors une période quadratique.

for( int i = 0; i< list.size(); i++ ) {
    list.get(i); //this starts everytime from the head node instead of previous node
}

Tandis que la boucle for each itère sur l'itérateur obtenu à partir de la liste liée et appelle sa méthode next (). L'itérateur conserve les états du dernier accès et ne part donc pas complètement de la tête à chaque fois.

for( Object item: list ) {
    //item element is obtained from the iterator's next method.
}
1
mickeymoon

L'une des meilleures raisons d'utiliser un itérateur sur la syntaxe i ++ est que toutes les structures de données ne prendront pas en charge l'accès aléatoire, encore moins de le faire bien fonctionner. Vous devriez également programmer la liste ou l'interface de collecte de sorte que, si vous décidiez par la suite qu'une autre structure de données serait plus efficace, vous pourrez l'échanger sans intervention chirurgicale massive. Dans ce cas (le cas du codage sur une interface), vous ne connaîtrez pas nécessairement les détails de la mise en œuvre et il est probablement plus sage de le reporter à la structure de données elle-même. 

1
Jason Tholstrup

L’une des raisons pour lesquelles j’ai appris à rester avec chacun est que cela simplifie les boucles imbriquées, en particulier sur les boucles à plus de 2 dimensions. Tous les i, j et k que vous pouvez finir par manipuler peuvent être déroutants très rapidement.

1
Ashton K

Utilisez JAD ou JD-GUI contre votre code généré et vous verrez qu'il n'y a pas de réelle différence. L'avantage du nouveau formulaire itérateur est qu'il semble plus propre dans votre base de code.

Modifier: Je vois dans les autres réponses que vous avez réellement voulu faire la différence entre utiliser get (i) et un itérateur. J'ai pris la question initiale pour faire la différence entre les anciennes et les nouvelles façons d'utiliser l'itérateur.

Utiliser get (i) et gérer votre propre compteur, en particulier pour les classes List, n’est pas une bonne idée, pour les raisons mentionnées dans la réponse acceptée.

1
Paul Wagland

+1 à ce que dit sfussenegger. Pour votre information, que vous utilisiez un itérateur explicite ou implicite (c’est-à-dire pour chacun) ne fera pas une différence de performance car ils compilent dans le même code octet. 

0
erturne