次のエディターは、Java コレクション トラバーサルのいくつかの方法の概要と詳細な比較を提供します。編集者はこれがとても良いものだと思ったので、皆さんの参考として今から共有します。エディターをフォローして見てみましょう
コレクション クラスの一般的なトラバース メソッド、イテレーターを使用して反復します:
Iterator it = list.iterator(); while(it.hasNext()) { Object obj = it.next(); }
マップ トラバース メソッド:
1 すべてのキーを取得します。キー Traverse
//Setset = map.keySet(); //得到所有key的集合 for (Integer in : map.keySet()) { String str = map.get(in);//得到每个key多对用value的值 }
2. Map.entrySet を通じてキーと値を走査するために反復子を使用します。特に容量が大きい場合に推奨されます。大きい
Iterator> it = map.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = it.next(); System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue()); }
4. Map.values() を介してすべての値を走査しますが、キーは走査しません
for (Map.Entryentry : map.entrySet()) { //Map.entry 映射项(键-值对) 有几个方法:用上面的名字entry //entry.getKey() ;entry.getValue(); entry.setValue(); //map.entrySet() 返回此映射中包含的映射关系的 Set视图。 System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue()); }
リスト走査メソッド:
最初のタイプ:
for (String v : map.values()) { System.out.println("value= " + v); }
for(Iterator iterator = list.iterator();iterator.hasNext();){ int i = (Integer) iterator.next(); System.out.println(i); }
Iterator iterator = list.iterator(); while(iterator.hasNext()){ int i = (Integer) iterator.next(); System.out.println(i); }
for (Object object : list) { System.out.println(object); }
データ要素はどのようにメモリに格納されるのでしょうか?
2 つの主な保存方法があります:
1. シーケンシャル ストレージ、ランダム アクセス (ディレクトリect アクセス):
この方法では、隣接するデータ要素が同じ場所に保存されます。隣接するメモリアドレス間では、メモリアドレス全体が連続しています。メモリアドレスは要素の位置に基づいて直接計算され、直接読み取ることができます。特定の位置にある要素を読み取る平均時間計算量は O(1) です。通常、arrayに基づいて実装されたコレクションのみがこの機能を備えています。 JavaはArrayListで表現されます。
この方法では、各データ要素は、その次の要素のメモリアドレスを含む必要がありません。メモリ アドレスは要素の位置に基づいて直接計算できません。要素は順番に読み取ることしかできません。特定の位置にある要素を読み取る平均時間計算量は O(n) です。主にリンクリストで表されます。 Java では LinkedList で表されます。
各トラバーサルメソッドの実装原理は何ですか?1. 従来のfor ループトラバーサル、カウンターベース:
トラバーサーはコレクションの外側にカウンターを保持し、最後の要素が読み取られたときに各位置の要素を順番に読み取ります。要素、停止します。主なことは、要素をその位置に従って読み取ることです。2. イテレータトラバーサル、イテレータ:
データセットの組み合わせのすべての具体的な実装は、通常、対応するイテレータを提供する必要があります。従来の for ループと比較して、Iterator では明示的なトラバーサル カウンタが不要になります。したがって、順次格納されたコレクションに基づくイテレーターは、位置によってデータに直接アクセスできます。チェーンされたストレージ コレクションに基づく Iterator の通常の実装では、現在のトラバース位置を保存する必要があります。次に、現在の位置に基づいてポインタを前後に移動します。
3、foreachループトラバーサル:
逆コンパイルされたバイトコードによれば、foreach も Iterator を使用して内部的に実装されていることがわかりますが、これらのコードは Java コンパイラによって生成されます。異なるストレージ方法に対する各トラバーサル方法のパフォーマンスはどのようなものですか?
1. カウンタに基づく従来の for ループトラバーサル:
要素の位置に基づいているため、位置によって読み取られます。したがって、シーケンシャル ストレージの場合、特定の位置にある要素の読み取りの平均時間計算量は O(1) であるため、コレクション全体を走査する平均時間計算量は O(n) であることがわかります。連鎖ストレージの場合、特定の場所にある要素の読み取りの平均時間計算量は O(n) であるため、コレクション全体を走査する平均時間計算量は O(n2) (n の 2 乗) です。位置による読み取り用の ArrayList コード: 要素の位置によって直接読み取ります。
for(int i = 0 ;i
transient Object[] elementData; public E get(int index) { rangeCheck(index); return elementData(index); } E elementData(int index) { return (E) elementData[index]; }
那么对于RandomAccess类型的集合来说,没有太多意义,反而因为一些额外的操作,还会增加额外的运行时间。但是对于Sequential Access的集合来说,就有很重大的意义了,因为Iterator内部维护了当前遍历的位置,所以每次遍历,读取下一个位置并不需要从集合的第一个元素开始查找,只要把指针向后移一位就行了,这样一来,遍历整个集合的时间复杂度就降低为O(n);
(这里只用LinkedList做例子)LinkedList的迭代器,内部实现,就是维护当前遍历的位置,然后操作指针移动就可以了:
代码:
public E next() { checkForComodification(); if (!hasNext()) throw new NoSuchElementException(); lastReturned = next; next = next.next; nextIndex++; return lastReturned.item; } public E previous() { checkForComodification(); if (!hasPrevious()) throw new NoSuchElementException(); lastReturned = next = (next == null) ? last : next.prev; nextIndex--; return lastReturned.item; }
3、foreach循环遍历:
分析Java字节码可知,foreach内部实现原理,也是通过Iterator实现的,只不过这个Iterator是Java编译器帮我们生成的,所以我们不需要再手动去编写。但是因为每次都要做类型转换检查,所以花费的时间比Iterator略长。时间复杂度和Iterator一样。
Iterator和foreach字节码如下:
//使用Iterator的字节码: Code: 0: new #16 // class java/util/ArrayList 3: dup 4: invokespecial #18 // Method java/util/ArrayList."":()V 7: astore_1 8: aload_1 9: invokeinterface #19, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 14: astore_2 15: goto 25 18: aload_2 19: invokeinterface #25, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 24: pop 25: aload_2 26: invokeinterface #31, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z 31: ifne 18 34: return //使用foreach的字节码: Code: 0: new #16 // class java/util/ArrayList 3: dup 4: invokespecial #18 // Method java/util/ArrayList." ":()V 7: astore_1 8: aload_1 9: invokeinterface #19, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 14: astore_3 15: goto 28 18: aload_3 19: invokeinterface #25, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 24: checkcast #31 // class loop/Model 27: astore_2 28: aload_3 29: invokeinterface #33, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z 34: ifne 18 37: return
各遍历方式的适用于什么场合?
1、传统的for循环遍历,基于计数器的:
顺序存储:读取性能比较高。适用于遍历顺序存储集合。
链式存储:时间复杂度太大,不适用于遍历链式存储的集合。
2、迭代器遍历,Iterator:
顺序存储:如果不是太在意时间,推荐选择此方式,毕竟代码更加简洁,也防止了Off-By-One的问题。
链式存储:意义就重大了,平均时间复杂度降为O(n),还是挺诱人的,所以推荐此种遍历方式。
3、foreach循环遍历:
foreach只是让代码更加简洁了,但是他有一些缺点,就是遍历过程中不能操作数据集合(删除等),所以有些场合不使用。而且它本身就是基于Iterator实现的,但是由于类型转换的问题,所以会比直接使用Iterator慢一点,但是还好,时间复杂度都是一样的。所以怎么选择,参考上面两种方式,做一个折中的选择。
Java的最佳实践是什么?
Java数据集合框架中,提供了一个RandomAccess接口,该接口没有方法,只是一个标记。通常被List接口的实现使用,用来标记该List的实现是否支持Random Access。
一个数据集合实现了该接口,就意味着它支持Random Access,按位置读取元素的平均时间复杂度为O(1)。比如ArrayList。
而没有实现该接口的,就表示不支持Random Access。比如LinkedList。
所以看来JDK开发者也是注意到这个问题的,那么推荐的做法就是,如果想要遍历一个List,那么先判断是否支持Random Access,也就是 list instanceof RandomAccess。
比如:
if (list instanceof RandomAccess) { //使用传统的for循环遍历。 } else { //使用Iterator或者foreach。 }
【相关推荐】
3.JAVA教程手册
以上がJava のさまざまな一般的なトラバーサル メソッドの詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。