JavaRush /Java Blog /Random-JA /コーヒーブレイク #137。For ループと Foreach - Java ではどちらが速いですか? Java マ...

コーヒーブレイク #137。For ループと Foreach - Java ではどちらが速いですか? Java マップの各エントリをループする 8 つの効率的な方法

Random-JA グループに公開済み

For ループと Foreach - Java ではどちらの方が高速ですか?

出典: Medium 数年前に仕事を探していたとき、面接で尋ねられた質問の 1 つは、forまたはforEach を使用してArrayListを反復処理する必要があるかどうかでした。 forEachforの設定の違いについての議論は長い間行われてきました。forEach の方が速いという印象を受けました。しかし、最終的には自分が間違っていたことに気づきました。参考までに、Java 1.5 で導入されたforEachループ(または改良されたforループ) は、反復子またはインデックス変数を完全に非表示にすることで、煩雑さとエラーの可能性を排除します。forforEachの唯一の実際的な違いは、インデックス付きオブジェクトの場合、インデックスにアクセスできないことだと思います。 コーヒーブレイク #137。 For ループと Foreach - Java ではどちらの方が高速ですか?  Java マップの各エントリをループする 8 つの効率的な方法 - 1
for(int i = 0; i < mylist.length; i++) {
 if(i < 5) {
 //do something
 } else {
 //do other stuff
 }
}
ただし、 forEachを使用してint 型の別のインデックス変数を作成できます。例えば:
int index = -1;
for(int myint : mylist) {
 index++;
 if(index < 5) {
 //do something
 } else {
 //do other stuff
 }
}
forEachを使用してリストを反復処理するforeachTest()メソッド を持つ単純なクラスを作成してみましょう。
import java.util.List;

public class ForEachTest {
	List<Integer> intList;

    public void foreachTest(){
        for(Integer i : intList){

        }
    }
}
このクラスをコンパイルすると、コンパイラーは内部でコードをイテレーター実装に変換します。javap -verbose IterateListTestを実行して、コンパイルされたコードを逆コンパイルしました。
public void foreachTest();
   descriptor: ()V
   flags: ACC_PUBLIC
   Code:
     stack=1, locals=3, args_size=1
        0: aload_0
        1: getfield      #19                 // Field intList:Ljava/util/List;
        4: invokeinterface #21,  1           // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
        9: astore_2
       10: goto          23
       13: aload_2
       14: invokeinterface #27,  1           // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
       19: checkcast     #33                 // class java/lang/Integer
       22: astore_1
       23: aload_2
       24: invokeinterface #35,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
       29: ifne          13
       32: return
     LineNumberTable:
       line 9: 0
       line 12: 32
     LocalVariableTable:
       Start  Length  Slot  Name   Signature
           0      33     0  this   Lcom/greekykhs/springboot/ForEachTest;
     StackMapTable: number_of_entries = 2
       frame_type = 255 /* full_frame */
         offset_delta = 13
         locals = [ class com/greekykhs/springboot/ForEachTest, top, class java/util/Iterator ]
         stack = []
       frame_type = 9 /* same */
上記のバイトコードから次のことがわかります。
  1. getfieldコマンドは、整数変数を取得するために使用されます。

  2. List.iterator を呼び出してイテレータ インスタンスを取得します。

  3. iterator.hasNextを呼び出します。true を返した場合は、iterator.nextメソッドを呼び出す必要があります。

パフォーマンステストを実行してみましょう。メインのIterateListTestメソッドでは、リストを作成し、for ループforEachループを使用してそれを反復処理しました。
import java.util.ArrayList;
import java.util.List;

public class IterateListTest {
	public static void main(String[] args) {
		List<Integer> mylist = new ArrayList<>();
        for (int i = 0; i < 1000000; i++) {
            mylist.add(i);
        }

        long forLoopStartTime = System.currentTimeMillis();
        for (int i = 0; i < mylist.size(); i++) {mylist.get(i);}

        long forLoopTraversalCost =System.currentTimeMillis()-forLoopStartTime;
        System.out.println("for loop traversal cost for ArrayList= "+ forLoopTraversalCost);

        long forEachStartTime = System.currentTimeMillis();
        for (Integer integer : mylist) {}

        long forEachTraversalCost =System.currentTimeMillis()-forEachStartTime;
        System.out.println("foreach traversal cost for ArrayList= "+ forEachTraversalCost);
	}
}
結果は次のとおりです。 コーヒーブレイク #137。 For ループと Foreach - Java ではどちらの方が高速ですか?  Java マップの各エントリを反復処理する 8 つの効率的な方法 - 2ご覧のとおり、forループのパフォーマンスはforEachループよりも優れています。ArrayListの代わりにLinkedListを使用すると、 forEach のパフォーマンスがLinkedListの方が優れていることがわかります。 ArrayList は内部で配列を使用して要素を格納します。配列はメモリの連続した領域であるため、時間計算量は O(1) です。これは、データがインデックスを通じて取得されるためです。 LinkedList は二重リンク リストを使用します。forループを使用してトラバーサルを実装すると、毎回リンク リストの先頭ノードから開始されるため、時間計算量は O(n*n) になります。

Java マップの各エントリをループする 8 つの効率的な方法

出典: Medium 先週、インターンから Java Map を反復する方法を尋ねられました。私は、とても簡単なので、この質問に対する答えはいつでも Google にあると答えました。しばらくして、彼女は StackOverflow 上のページのアドレスを私に送ってくれました。そして、非常に多くの人がこの問題に注目していることが分かりました。したがって、私は反復の問題に焦点を当て、それを行うためのいくつかの方法を共有することにしました。 コーヒーブレイク #137。 For ループと Foreach - Java ではどちらの方が高速ですか?  Java マップの各エントリを反復処理する 8 つの効率的な方法 - 3

1. イテレータと Map.Entry の使用

@Test
public void test1_UsingWhileAndMapEntry(){
    long i = 0;
    Iterator<Map.Entry<Integer, Integer>> it = map.entrySet().iterator();
    while (it.hasNext()) {
        Map.Entry<Integer, Integer> pair = it.next();
        i += pair.getKey() + pair.getValue();
    }
    System.out.println(i);
}

2. foreach と Map.Entry の使用

@Test
public void test2_UsingForEachAndMapEntry(){
    long i = 0;
    for (Map.Entry<Integer, Integer> pair : map.entrySet()) {
        i += pair.getKey() + pair.getValue();
    }
    System.out.println(i);
}

3. Java 8 からの foreach の使用

@Test
public void test3_UsingForEachAndJava8(){
    final long[] i = {0};
    map.forEach((k, v) -> i[0] += k + v);
    System.out.println(i[0]);
}

4. keySet と foreach の使用

@Test
public void test4_UsingKeySetAndForEach(){
    long i = 0;
    for (Integer key : map.keySet()) {
        i += key + map.get(key);
    }
    System.out.println(i);
}

5. keySet とイテレータの使用

@Test
public void test5_UsingKeySetAndIterator(){
    long i = 0;
    Iterator<Integer> it = map.keySet().iterator();
    while (it.hasNext()) {
        Integer key = it.next();
        i += key + map.get(key);
    }
    System.out.println(i);
}

6. for と Map.Entry の使用

@Test
public void test6_UsingForAndIterator(){
    long i = 0;
    for (Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator(); entries.hasNext(); ) {
        Map.Entry<Integer, Integer> entry = entries.next();
        i += entry.getKey() + entry.getValue();
    }
    System.out.println(i);
}

7. Java 8 ストリーム API の使用

@Test
public void test7_UsingJava8StreamApi(){
    System. out .println(map.entrySet().stream().mapToLong(e -> e.getKey() + e.getValue()).sum());
}

8. Java 8 Stream APIの並列使用

@Test
public void test8_UsingJava8StreamApiParallel(){
    System. out .println(map.entrySet().parallelStream().mapToLong(e -> e.getKey() + e.getValue()).sum());
}

各方法の速度の比較:

public final static Integer SIZE = 1000000;
public Map<Integer, Integer> map = toMap();
public Map<Integer, Integer> toMap(){
    map = new HashMap<>(SIZE);
    for (int i = 0; i < SIZE; i++) {
        map.put(i, i);
    }
    return map;
}
我々が得る: コーヒーブレイク #137。 For ループと Foreach - Java ではどちらの方が高速ですか?  Java マップの各エントリをループする 8 つの効率的な方法 - 4コーヒーブレイク #137。 For ループと Foreach - Java ではどちらの方が高速ですか?  Java マップの各エントリを反復処理する 8 つの効率的な方法 - 5コーヒーブレイク #137。 For ループと Foreach - Java ではどちらの方が高速ですか?  Java マップの各エントリを反復処理する 8 つの効率的な方法 - 6

結論

データを比較すると、数値が小さい場合は方法 6 と方法 8 が最も時間がかかりますが、数値が大きい場合は方法 8 が同時に実行されるため最も時間がかかりません。興味深いのは、テストの実行順序が常に for -> while -> foreach/stream であることですが、その理由はわかりません:(
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION