Prestazioni del ciclo for tradizionale vs Iterator / foreach in Java

Sono disponibili risultati di test delle prestazioni nel confronto tra tradizionale per loop vs Iterator mentre si attraversano ArrayList, HashMap e altre raccolte?

O semplicemente perché dovrei usare Iterator over per loop o viceversa?

Supponendo che questo è ciò che intendevi:

 // traditional for loop for (int i = 0; i < collection.size(); i++) { T obj = collection.get(i); // snip } // using iterator Iterator 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 } 

Iterator è più veloce per le collezioni senza accesso casuale (ad es. TreeSet, HashMap, LinkedList). Per le matrici e le liste di array, le differenze di prestazioni dovrebbero essere trascurabili.

Edit: Credo che il micro-benchmarking sia la radice del male, proprio come l’ottimizzazione iniziale. Ma ripeto, penso sia bello avere un sentimento per le implicazioni di cose così banali. Quindi ho eseguito un piccolo test :

  • scorrere su LinkedList e ArrayList rispettivamente
  • con 100.000 stringhe “casuali”
  • riassumendo la loro lunghezza (solo qualcosa per evitare che il compilatore ottimizzi l’intero ciclo)
  • utilizzando tutti e 3 gli stili di loop (iteratore, per ciascuno, per con contatore)

I risultati sono simili per tutti tranne “for with counter” con LinkedList. Tutti gli altri cinque hanno impiegato meno di 20 millisecondi per scorrere l’intera lista. Usare list.get(i) su una list.get(i) 100.000 volte ha impiegato più di 2 minuti (!) Per completare (60.000 volte più lento). Wow! 🙂 Quindi è meglio usare un iteratore (usando esplicitamente o implicitamente per ciascuno), specialmente se non si conosce il tipo e la dimensione della lista con cui si sta trattando.

La prima ragione per usare un iteratore è la correttezza ovvia . Se usi un indice manuale, potrebbero esserci degli errori off-by-one molto innocui che puoi vedere solo se guardi da vicino: hai iniziato da 1 o da 0? Hai finito alla length - 1 ? Hai usato < o < = ? Se si utilizza un iteratore, è molto più facile vedere che sta davvero iterando l'intero array. "Dì quello che fai, fai quello che dici."

La seconda ragione è l'accesso uniforms a diverse strutture di dati. È ansible accedere in modo efficiente a un array tramite un indice, ma è meglio attraversare un elenco collegato ricordando l'ultimo elemento a cui si accede (altrimenti si ottiene " Shlemiel il pittore "). Una hashmap è ancora più complicata. Fornendo un'interfaccia uniforms da queste e da altre strutture di dati (ad esempio, è ansible anche attraversare gli alberi), si ottiene di nuovo un'ovvia correttezza. La logica di attraversamento deve essere implementata una sola volta e il codice che la utilizza può concisamente "dire quello che fa e fare ciò che dice".

Le prestazioni sono simili nella maggior parte dei casi.

Tuttavia, ogni volta che un codice riceve un elenco e scorre su di esso, esiste un caso ben noto:
l’Iterator è decisamente migliore per tutte le implementazioni di List che non implementano RandomAccess (esempio: LinkedList).

Il motivo è che per questi elenchi, l’accesso a un elemento per indice non è un’operazione a tempo costante.

Quindi puoi anche considerare l’Iterator più robusto (per i dettagli di implementazione).


Come sempre, le prestazioni non dovrebbero essere problemi di leggibilità.
Il ciclo foreach di java5 è un grande successo su questo aspetto 🙂

Non ci credo

 for (T obj : collection) { 

calcola .size () ogni volta attraverso il ciclo ed è quindi più veloce di

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

Uno dei migliori motivi per utilizzare un iteratore rispetto alla syntax i ++ è che non tutte le strutture di dati supporteranno l’accesso casuale e tanto meno lo faranno funzionare bene. Dovresti anche programmare l’elenco o l’interfaccia di raccolta in modo che se in seguito decidessi che un’altra struttura di dati sarebbe più efficiente, potresti essere in grado di sostituirla senza un intervento chirurgico massiccio. In quel caso (il caso della codifica su un’interfaccia) non si conoscono necessariamente i dettagli dell’implementazione ed è probabilmente più saggio rimandarlo alla struttura dati stessa.

Uno dei motivi per cui ho imparato ad attenermi a ciascuno è che semplifica i cicli annidati, specialmente su più di 2 anelli dimensionali. Tutti gli i, i j e i k che potresti finire per manipolare possono creare confusione molto rapidamente.

Usa JAD o JD-GUI contro il tuo codice generato, e vedrai che non c’è alcuna differenza reale. Il vantaggio della nuova forma di iteratore è che sembra più pulito nella base di codice.

Edit : Vedo dalle altre risposte che in realtà intendevi la differenza tra l’utilizzo di get (i) contro un iteratore. Ho preso la domanda iniziale per indicare la differenza tra il vecchio e il nuovo modo di usare l’iteratore.

Usare get (i) e mantenere il proprio contatore, specialmente per le classi List non è una buona idea, per le ragioni menzionate nella risposta accettata.

Sì, fa una differenza sulle raccolte che non sono basate su accessi casuali come LinkedList. Una lista collegata internamente è implementata da nodes che puntano al successivo (partendo da un nodo principale).

Il metodo get (i) in una lista collegata inizia dal nodo head e naviga attraverso i collegamenti fino al nodo ith. Quando si esegue iterazione sull’elenco collegato utilizzando un ciclo for tradizionale, si ricomincia dal nodo principale ogni volta, quindi l’attraversamento complessivo diventa il tempo quadratico.

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

Mentre per ogni ciclo itera l'iteratore ottenuto dall'elenco collegato e chiama il suo metodo next (). L'iteratore mantiene gli stati dell'ultimo accesso e quindi non inizia da capo ogni volta.

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

+1 a ciò che ha detto sfussenegger. Cordiali saluti, se si utilizza un iteratore esplicito o uno implicito (vale a dire per ciascuno) non farà una differenza di prestazioni perché compilano lo stesso codice byte.