web-dev-qa-db-ja.com

Javaにおける従来のforループとIterator / foreachのパフォーマンス

ArrayList、HashMap、およびその他のコレクションを走査しながら、従来のforループとIteratorを比較する際に利用可能なパフォーマンステスト結果はありますか?

それとも、なぜループの反復子を使用する必要があるのですか?

59
Harish

これがあなたが意図したものであると仮定します:

_// 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
}
_

イテレータは、ランダムアクセスのないコレクション(TreeSet、HashMap、LinkedListなど)の方が高速です。配列とArrayListの場合、パフォーマンスの違いはごくわずかです。

編集:マイクロベンチマークは、初期の最適化と同様に、ほとんどの悪の根源であると考えています。しかし、それでもまた、そのような非常に些細なことの意味合いについて感じているのは良いことだと思います。したがって、私は 小さなテスト を実行しました:

  • linkedListとArrayListをそれぞれ繰り返し処理する
  • 100,000個の「ランダム」文字列
  • それらの長さを合計します(コンパイラーがループ全体を最適化することを避けるための何か)
  • 3つのループスタイルすべて(イテレータ、それぞれ、forカウンタ付き)を使用する

結果は、LinkedListを使用した「for with counter」以外のすべてで類似しています。他の5つはすべて、リスト全体を繰り返すのに20ミリ秒もかかりませんでした。 LinkedListでlist.get(i)を100,000回使用すると、完了するまでに2分(!)以上かかりました(60,000倍遅くなりました)。うわー! :)したがって、特に、処理するリストのタイプとサイズがわからない場合は、反復子を使用することをお勧めします(それぞれに対して明示的または暗黙的に使用)。

80
sfussenegger

イテレータを使用する最初の理由は、明らかな正確さです。手動インデックスを使用すると、非常に無害なオフバイワンエラーが発生する場合がありますが、これは非常によく見ると表示されます。1から始めたのか0から始めたのか。 length - 1で終了しましたか? <または<=を使用しましたか?イテレータを使用すると、配列全体を実際に繰り返していることがわかりやすくなります。 「あなたがすることを言って、あなたが言うことをしてください。」

2番目の理由は、異なるデータ構造への均一なアクセスです。配列はインデックスを介して効率的にアクセスできますが、リンクされたリストは、最後にアクセスした要素を覚えておくと最もよく走査できます(そうでない場合は、「 Shlemiel the Painter 」を取得します)。ハッシュマップはさらに複雑です。これらのデータ構造や他のデータ構造から統一されたインターフェイスを提供することにより(たとえば、ツリートラバーサルを実行することもできます)、再び明確な正確さが得られます。トラバースロジックは一度だけ実装する必要があり、それを使用するコードは簡潔に「それが何をするのかを言い、それが言うことをする」ことができます。

22
Svante

ほとんどの場合、パフォーマンスは似ています。

ただし、コードがリストを受信して​​ループするたびに、よく知られたケースがあります。
Iteratorは、RandomAccessを実装していないすべてのList実装に適しています(例:LinkedList)。

その理由は、これらのリストでは、インデックスによる要素へのアクセスは一定時間の操作ではないためです。

そのため、イテレータを(実装の詳細まで)より堅牢であると考えることもできます。


いつものように、パフォーマンスは読みやすさの問題を隠すべきではありません。
Java5 foreachループはその面で大ヒットです:-)

4
KLE

信じられない

for (T obj : collection) {

ループのたびに.size()を計算するため、より高速です

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

はい、LinkedListのようなランダムアクセスベースではないコレクションに違いをもたらします。リンクリストは、次を指すノードによって内部的に実装されます(ヘッドノードから開始)。

リンクリストのget(i)メソッドは、ヘッドノードから開始し、リンクをi番目のノードまでナビゲートします。従来のforループを使用してリンクリストを反復すると、毎回ヘッドノードから再び開始するため、全体的なトラバーサル時間が2次の時間になります。

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

For eachループは、リンクリストから取得したイテレータを反復処理し、そのnext()メソッドを呼び出します。イテレータは最後のアクセスの状態を維持するため、毎回先頭から開始するわけではありません。

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

I ++構文でイテレータを使用する最も良い理由の1つは、すべてのデータ構造がランダムアクセスをサポートするわけではなく、それがうまく機能することです。また、別のデータ構造の方が効率的であると後で判断した場合に、大規模な手術なしで交換できるように、リストまたはコレクションインターフェイスをプログラミングする必要があります。その場合(インターフェイスへのコーディングの場合)、実装の詳細を必ずしも知る必要はなく、おそらくデータ構造自体にそれを延期する方が賢明です。

1
Jason Tholstrup

For eachに固執することを学んだ理由の1つは、ネストされたループ、特に2次元以上のループを単純化することです。最終的に操作する可能性のあるすべてのi、j、およびkは、すぐに混乱する可能性があります。

1
Ashton K

生成されたコードに対して [〜#〜] jad [〜#〜] または JD-GUI を使用すると、実際の違いはないことがわかります。新しいイテレータ形式の利点は、コードベースでよりきれいに見えることです。

編集:他の回答から、実際にはget(i)を使用することとイテレーターを使用することの違いを意味していることがわかります。元の質問は、イテレータを使用する古い方法と新しい方法の違いを意味します。

特にListクラスの場合、get(i)を使用して独自のカウンターを維持することは、受け入れられた回答に記載されている理由から、良いアイデアではありません。

1
Paul Wagland

sfusseneggerが言ったことに+1。参考までに、明示的なイテレータを使用するか、暗黙的なイテレータを使用するか(つまり、各イテレータ)は、同じバイトコードにコンパイルされるため、パフォーマンスの違いは生じません。

0
erturne