集合遍历方式的总结

297 阅读6分钟

一、遍历集合的方式

1、普通for循环

实现原理: 在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后,停止。按元素的位置来读取元素;

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

2、增强for循环

实现原理:根据反编译的字节码可以发现,foreach内部也是采用了Iterator的方式实现,只不过Java编译器帮我们生成了这些代码;

for (Integer integer : list) {
}

3、迭代器遍历

实现原理:相比于普通for循环,Iterator取缔了显式的遍历计数器。基于顺序存储集合的Iterator可以直接按位置访问数据;基于链式存储集合的Iterator,需要保存当前遍历的位置,然后根据当前位置来向前或者向后移动指针;

啥是迭代器?集合顶级接口Collection继承Iterable,在Iterable接口中存在一个内部接口Iterator,在每个集合的子类中都实现了接口Iterator中的方法;

基本方法为:
hasNext()   //是否存在下一个对象
next()      //下一个对象
remove()    //移除对象
forEachRemaining()   //遍历 

对于List接口下的实现类可以获得一种独特的迭代器对象ListIterator
public interface ListIterator<E> extends Iterator<E> 
    
ListIterator 继承 Iterator
1) add(E e)  将指定的元素插入列表,插入位置为迭代器当前位置之前
2) set(E e)  迭代器返回的最后一个元素替换参数e
3) hasPrevious()  迭代器当前位置,反向遍历集合是否含有元素
4) previous()  迭代器当前位置,反向遍历集合,下一个元素
5) previousIndex()  迭代器当前位置,反向遍历集合,返回下一个元素的下标
6) nextIndex()  迭代器当前位置,返回下一个元素的下标
区别:
Iterator可以迭代所有集合;ListIterator 只能用于List及其子类;
ListIterator 有 add()方法,可以向 List 中添加对象;Iterator 不能;
ListIterator 有 set()方法,可以实现对 List 的修改;Iterator 仅能遍历,不能修改; 
ListIterator 有 hasPrevious() 和 previous() 方法,可以实现逆向遍历;Iterator不可以;
ListIterator 有 nextIndex() 和previousIndex() 方法,可定位当前索引的位置;Iterator不可以;  

注意:使用增强for循环遍历 最终都会转换成迭代器遍历
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
    iterator.next();
}

//使用Iterator的字节码:
    Code:
       0: new           #16       // class java/util/ArrayList
       3: dup
       4: invokespecial #18        // Method java/util/ArrayList."<init>":()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."<init>":()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

4、数据存储的方式

数据存储主要分为二种方式,同时也因为存储方式的不同导致在遍历的时候会有性能上的区别;

顺序存储:相邻的数据元素存放于相邻的内存地址中,整块内存地址是连续的,可以根据元素的位置直接计算出内存地址,直接进行读取。读取一个特定位置元素的平均时间复杂度为O(1),正常来说,只有基于数组实现的集合,才有这种特性;

链式存储:每一个数据元素在内存中都不要求处于相邻的位置,每个数据元素包含它上/下一个元素的内存地址。不可以根据元素的位置直接计算出内存地址,只能按顺序读取元素。读取一个特定位置元素的平均时间复杂度为O(n),主要以链表为代表;

二、List集合的遍历

在单列集合中,主要是探讨ArrayList与LinkedList的遍历方式,由实现数据结构可知,ArrayList底层是由数组实现,可以通过索引快速的获取到元素,同时ArrayList实现了RandomAccess接口,关于官网对这个接口的定义是:RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。也就是说,实现了这个接口的集合是支持快速随机访问策略的,同时还特别声明,如果同时实现List接口,使用for循环会优于使用迭代器获取数据;

注意:不要在 foreach 循环里进行元素的 remove/add 操作,会引发并发修改异常java.util.ConcurrentModificationException;    

ArrayList底层数据结构是由数组实现,在存储方式上属于顺序存储,在源码中实现查找也是根据索引查找对应的元素;

LinkedList底层数据结构是由链表实现,每次在使用索引去获得对象的时候,底层会先组判断由链表的哪一头开始整个链路会比较短;但是对于这种链式存储方式,因为Iterator内部维护了当前遍历的位置,所以每次遍历,读取下一个位置并不需要从集合的第一个元素开始查找,只要把指针向后移一位就行了,这样一来,遍历整个集合的时间复杂度就降低为O(n);

三、Map集合的遍历方式

在遍历Map的时候,知道Map中有提供方法,可以获取集合的所有key、所有的键值对对象Map<K,V>,当然也有提供迭代器遍历的方式;

//获取所有的Key
Set<Integer> keySet = map.keySet();
Iterator<Integer> iterator = map.keySet().iterator();

//获取所有的Entry<K, V>
Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
Iterator<Map.Entry<Integer, String>> entrySetIterator = entrySet.iterator();

//同时获得key、vaule
for (Map.Entry<Integer, String> entry : map.entrySet()) {
    entry.getKey();
    entry.getKey();
}

鄙人不才,在您面前献丑只愿与您结伴而行,文章若有不当之处,望大佬指点一二;如果我对您有帮助的话,还希望您能点赞分享,成长是一场苦涩的独自修行,我很需要您的陪伴与支持,这也是鄙人不断前行的根本动力,让我们在互相陪伴见证彼此生长的同时能够感染身边最亲近的人一同成长,鄙人在此叩谢!