一、遍历方式

ArrayList支持三种遍历方式。

1、第一种,随机访问,它是通过索引值去遍历

由于ArrayList实现了RandomAccess接口,它支持通过索引值去随机访问元素。

代码如下:

// 基本的for
for (int i = 0; i < size; i++)
{
    value = list.get(i);
}

2、第二种,foreach语句

foreach语句是java5的新特征之一,在遍历数组、集合方面,foreach为开发人员提供了极大的方便。
代码如下:
for (Integer integer : list)
{
    value = integer;
}

3、第三种,Iterator迭代器方式

迭代器是一种模式,它可以使得对于序列类型的数据结构的遍历行为与被遍历的对象分离,即我们无需关心该序列的底层结构是什么样子的。只要拿到这个对象,使用迭代器就可以遍历这个对象的内部。

代码如下:

for (Iterator<Integer> iterator = list.iterator(); iterator.hasNext();)
{
    value = iterator.next();           
}
 

二、几种遍历方式效率的比较

要想知道上面几种遍历方式的效率如何,最简单的办法,就是我们自己编写代码来测试它。

测试代码如下:

/**
* 测试ArrayList中几种循环的效率
*
* @author Administrator
* @version 1.0
*/
public class TestArrayListLoop
{
    public static void main(String[] args)
    {
        // 准备数据阶段
        List<Integer> list = new ArrayList<Integer>();
        for (int i = 0; i < 100000; i++)
        {
            list.add(i);
        }         // 测试阶段
        int runCounts = 1000; // 执行次s数
        int listSize = list.size();
        int value;
       
        // For循环的测试
        long startTime1 = System.currentTimeMillis();
        for (int i = 0; i < runCounts; i++)
        {
            loopOfFor(list);
        }
        long endTime1 = System.currentTimeMillis();
       
        // Foreach循环的测试
        long startTime2 = System.currentTimeMillis();
        for (int i = 0; i < runCounts; i++)
        {
            loopOfForeach(list);
        }
        long endTime2 = System.currentTimeMillis();
       
        // Iterator迭代器的测试
        long startTime3 = System.currentTimeMillis();
        for (int i = 0; i < runCounts; i++)
        {
            loopOfIterator(list);
        }
        long endTime3 = System.currentTimeMillis();
       
        System.out.println("loopOfFor: " + (endTime1-startTime1)+ "ms");
        System.out.println("loopOfForeach: "+ (endTime2-startTime2)+ "ms");
        System.out.println("loopOfIterator: "+ (endTime3-startTime3)+ "ms");
    }     /**
     * 由于ArrayList实现了RandomAccess接口,它支持通过索引值去随机访问元素。
     * @param list
     */
    public static void loopOfFor(List<Integer> list)
    {
        int value;
        int size = list.size();
        // 基本的for
        for (int i = 0; i < size; i++)
        {
            value = list.get(i);
        }
    }
   
    /**
     * 使用forecah方法遍历数组
     * @param list
     */
    public static void loopOfForeach(List<Integer> list)
    {
        int value;
        // foreach
        for (Integer integer : list)
        {
            value = integer;
        }
    }
   
    /**
     * 通过迭代器方式遍历数组
     * @param list
     */
    public static void loopOfIterator(List<Integer> list)
    {
        int value;
        // iterator
        for (Iterator<Integer> iterator = list.iterator(); iterator.hasNext();)
        {
            value = iterator.next();           
        }
    }
}
输出结果:
第一次:

loopOfFor: 72ms

loopOfForeach: 89ms

loopOfIterator: 91ms

第二次:

loopOfFor: 70ms

loopOfForeach: 90ms

loopOfIterator: 87ms

从运行结果可以看出,loopOfFor耗时最少,效率最高,但是loopOfForeach和loopOfIterator之间的关系,有点不明确。
因此,我决定增大运行次数,设置runCounts = 10000。
输出结果:
第一次:

loopOfFor: 668ms

loopOfForeach: 760ms

loopOfIterator: 679ms

第二次:

loopOfFor: 672ms

loopOfForeach: 751ms

loopOfIterator: 678ms

这次发现,loopOfForeach效率低于loopOfIterator。
总结:从实验结果来看,在遍历ArrayList中,效率最高的是loopOfFor,loopOfForeach和loopOfIterator之间关系不明确,但在增大运行次数时,loopOfIterator效率高于loopOfForeach。

三、效率分析

1、为什么基本的for循环效率高于Iterator遍历?

ArrayList实现了RandomAccess接口,RandomAccess接口为ArrayList带来了什么好处呢?
我们查看一下RandomAccess的源码文档,发现有这样一段描述:

As a rule of thumb, a List implementation should implement this interface if, for typical instances of the class, this loop:

     for (int i=0, n=list.size(); i < n; i++)
list.get(i);

runs faster than this loop:

     for (Iterator i=list.iterator(); i.hasNext(); )
i.next();
从描述中,可以看出实现RandomAccess接口的集合类,使用for循环的效率会比Iterator高。
RandomAccess接口为ArrayList带来的好处:
  • 1、可以快速随机访问集合。
  • 2、使用快速随机访问(for循环)效率可以高于Iterator。

2、为什么foreach循环效率与Iterator效率有点暧昧?

通过调试loopOfForeach方法代码,我们发现它的执行顺序为
1、return new Itr();
其源码为:
public Iterator<E> iterator() {
        return new Itr();
}
2、hasNext();
3、next();
4、value = integer;
5、hasNext();
6、next();
7、value = integer;
从中,我们可以大致得出一个结论:foreach不是关键字,它的关键字是for,它的语句是由iterator实现的。
forEach就是为了让用iterator循环访问的形式简单,写起来更方便。
注意:
当然功能不太全,例如遇到从结构上对列表进行list.add()和list.remove()等方法(包括只要能修改集合中的modCount字段的方法),迭代器都会抛出ConcurrentModificationException异常,除非使用iterator自身的remove、add方法。
在ArrayList中,它内部实现了一个Iterator<E>类,将其作为ArrayList的内部类,然后通过iterator()方法创建该内部类,该内部类只实现了remove()方法,所以碰到需要list.remove()元素时,不要使用foreach,可以使用for、或者iterator。
ArrayList.iterator()代码结构:
public Iterator<E> iterator()
{
    return new Itr();
}
// An optimized version of AbstractList.Itr
private class Itr implements Iterator<E>
{
    int    cursor;                        // index of next element to return
    int    lastRet                = -1;        // index of last element returned; -1 if no such
    int    expectedModCount    = modCount;
   
    public boolean hasNext()
    {
        return cursor != size;
    }     @SuppressWarnings("unchecked")
    public E next()
    {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }     public void remove()
    {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();
        try
        {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex)
        {
            throw new ConcurrentModificationException();
        }     }     @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer)
    {
        Objects.requireNonNull(consumer);
        final int size = ArrayList.this.size;
        int i = cursor;
        if (i >= size)
        {
            return;
        }
        final Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
        {
            throw new ConcurrentModificationException();
        }
        while (i != size && modCount == expectedModCount)
        {
            consumer.accept((E) elementData[i++]);
        }
        // update once at end of iteration to reduce heap write traffic
        cursor = i;
        lastRet = i - 1;
        checkForComodification();
    }     final void checkForComodification()
    {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

四、扩展

1、基本的for循环的效率一定比iterator迭代器的高吗?

不一定,主要还要看集合的数据结构组成。例如,ArrayList和LinkedList中就不同
  • ArrayList实现了RandomAccess随机访问接口,因此它对随机访问的速度快,而基本的for循环中的get()方法,采用的即是随机访问的方法,因而在ArrayList中,for循环速度快。
  • LinkedList采取的是顺序访问方式,iterator中的next()方法,采用的即是顺序访问方法,因此在LinkedList中,使用iterator的速度较快。

LinkedList中的结论正确吗?我们做个实验,测试一下就会水落石出。

代码如下:

public class TestLinkedListLoop
{
    public static void main(String[] args)
    {
        // 准备数据阶段
        List<Integer> list = new LinkedList<Integer>();
        for (int i = 0; i < 10000; i++)
        {
            list.add(i);
        }         // 测试阶段
        int runCounts = 10; // 执行次s数
        int listSize = list.size();
        int value;
       
        // For循环的测试
        long startTime1 = System.currentTimeMillis();
        for (int i = 0; i < runCounts; i++)
        {
            loopOfFor(list);
        }
        long endTime1 = System.currentTimeMillis();
       
        // Foreach循环的测试
        long startTime2 = System.currentTimeMillis();
        for (int i = 0; i < runCounts; i++)
        {
            loopOfForeach(list);
        }
        long endTime2 = System.currentTimeMillis();
       
        // Iterator迭代器的测试
        long startTime3 = System.currentTimeMillis();
        for (int i = 0; i < runCounts; i++)
        {
            loopOfIterator(list);
        }
        long endTime3 = System.currentTimeMillis();
       
        System.out.println("loopOfFor: " + (endTime1-startTime1)+ "ms");
        System.out.println("loopOfForeach: "+ (endTime2-startTime2)+ "ms");
        System.out.println("loopOfIterator: "+ (endTime3-startTime3)+ "ms");
    }     /**
     * 由于ArrayList实现了RandomAccess接口,它支持通过索引值去随机访问元素。
     * @param list
     */
    public static void loopOfFor(List<Integer> list)
    {
        int value;
        int size = list.size();
        // 基本的for
        for (int i = 0; i < size; i++)
        {
            value = list.get(i);
        }
    }
   
    /**
     * 使用forecah方法遍历数组
     * @param list
     */
    public static void loopOfForeach(List<Integer> list)
    {
        int value;
        // foreach
        for (Integer integer : list)
        {
            value = integer;
        }
    }
   
    /**
     * 通过迭代器方式遍历数组
     * @param list
     */
    public static void loopOfIterator(List<Integer> list)
    {
        int value;
        // iterator
        for (Iterator<Integer> iterator = list.iterator(); iterator.hasNext();)
        {
            value = iterator.next();           
        }
    }
}
输出结果:

loopOfFor: 332ms
loopOfForeach: 5ms

loopOfIterator: 4ms

出输出结果可以看出,在LinkedList中,iterator迭代器方式的速度比基本的for快。因而也证明了前面的结论是正确的。
从数据结构角度分析:
  • for循环适合访问顺序存储结构,可以根据下标快速获取指定元素(即支持随机访问)。
  • 而Iterator 适合访问链式存储结构,因为迭代器是通过next()和Pre()来定位的,但它也可以访问顺序存储结构的集合。

2、for、foreach、iterator之间的差别

1)形式差别

这个就不讲了,见之前的代码,就知道了。

2)条件差别

for:需要知道集合或数组的大小,而且需要是有序的,不然无法遍历;
foreach、iterator:都不需要知道集合或数组的大小,他们都是得到集合内的每个元素然后进行处理。

3)多态差别

for、foreach:都需要先知道集合的类型,甚至是集合内元素的类型,即需要访问内部的成员,不能实现态;
iterator:是一个接口类型,它不关心集合或者数组的类型,而且它还能随时修改和删除集合的元素。
举个例子:
public void display(Iterator<object> it)
{
    while(it.hasNext())
    {  
        system.out.print(it.next()+"");
    }
}          

当我们需要遍历不同的集合时,我们只需要传递集合的iterator(如arr.iterator())看懂了吧,这就是iterator的好处,他不包含任何有关他所遍历的序列的类型信息,能够将遍历序列的操作与序列底层的结构分离。迭代器统一了对容器的访问方式。这也是接口的解耦的最好体现。

4)用法差别

for循环:一般用来处理比较简单的有序的,可预知大小的集合或数组
foreach:可用于遍历任何集合或数组,而且操作简单易懂,他唯一的不好就是需要了解集合内部类型

iterator:是最强大的,它可以随时修改或者删除集合内部的元素,并且是在不需要知道元素和集合的类型的情况下进行的(原因可参考第三点:多态差别),当你需要对不同的容器实现同样的遍历方式时,迭代器是最好的选择!

 

参考:

1、Java中迭代列表中数据时几种循环写法的效率比较

2、JAVA ArrayList详细介绍(示例)

3、Java迭代器(转)(iterator详解以及和for循环的区别)

4、for 、foreach和iterator的区别

专题三、ArrayList遍历方式以及效率比较的更多相关文章

  1. Java中List集合的三种遍历方式(全网最详)

    List集合在Java日常开发中是必不可少的,只要懂得运用各种各样的方法就可以大大提高我们开发的效率,适当活用各种方法才会使我们开发事半功倍. 我总结了三种List集合的遍历方式,下面一一来介绍. 首 ...

  2. set的三种遍历方式-----不能用for循环遍历(无序)

    set的三种遍历方式,set遍历元素 list 遍历元素 http://blog.csdn.net/sunrainamazing/article/details/71577662 set遍历元素 ht ...

  3. for 、foreach 、iterator 三种遍历方式的比较

    习惯用法 for.foreach循环.iterator迭代器都是我们常用的一种遍历方式,你可以用它来遍历任何东西:包括数组.集合等 for 惯用法: List<String> list = ...

  4. 基于Java的二叉树的三种遍历方式的递归与非递归实现

    二叉树的遍历方式包括前序遍历.中序遍历和后序遍历,其实现方式包括递归实现和非递归实现. 前序遍历:根节点 | 左子树 | 右子树 中序遍历:左子树 | 根节点 | 右子树 后序遍历:左子树 | 右子树 ...

  5. Map三种遍历方式

    Map三种遍历方式 package decorator; import java.util.Collection; import java.util.HashMap; import java.util ...

  6. 大数据学习day13------第三阶段----scala01-----函数式编程。scala以及IDEA的安装,变量的定义,条件表达式,for循环(守卫模式,推导式,可变参数以及三种遍历方式),方法定义,数组以及集合(可变和非可变),数组中常用的方法

    具体见第三阶段scala-day01中的文档(scala编程基础---基础语法)  1. 函数式编程(https://www.cnblogs.com/wchukai/p/5651185.html): ...

  7. Java中list对象的三种遍历方式

    1.增强for循环 for(String str : list) {//其内部实质上还是调用了迭代器遍历方式,这种循环方式还有其他限制,不建议使用. System.out.println(str); ...

  8. java map遍历方式及效率

    本文转载自Java Map遍历方式的选择. 只给出遍历方式及结论.测试数据可以去原文看. 如果你使用HashMap 同时遍历key和value时,keySet与entrySet方法的性能差异取决于ke ...

  9. Map的三种遍历方式

    对于Map的三种方式遍历 1.keySet() 2.values() 3.entrySet()三种方式得到Set之后,都可以使用 foreach或者iterator, 不能使用for,因为数据结构决定 ...

随机推荐

  1. Codeforces Round #200 (Div. 1)D. Water Tree dfs序

    D. Water Tree Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/contest/343/problem/ ...

  2. Android设备上i-jetty环境的搭建-手机上的web服务器

    本文主要跟大家分享如何将一台Android设备打造成一个web服务器使用. 编译i-jetty 1.将源码download下来,http://code.google.com/p/i-jetty/dow ...

  3. Introdution to 3D Game Programming With DirectX11 第11章 习题解答

    11.1 这道题要注意使用了line strip,由于曾经一直用triangle list,所以在几何渲染的时候easy算错定点描绘的顺序. 贴一些代码,大概就能把这个问题解释清楚了,由于框架还不是特 ...

  4. android学习日记03--常用控件button/imagebutton

    常用控件 控件是对数据和方法的封装.控件可以有自己的属性和方法.属性是控件数据的简单访问者.方法则是控件的一些简单而可见的功能.所有控件都是继承View类 介绍android原生提供几种常用的控件bu ...

  5. 优麒麟(UbuntuKylin)不是国产Linux操作系统

    2014年5月10日,CCTV新闻频道"新闻直播间"栏目播报了"谁来替代Windows XP,工信部希望用户使用国产操作系统"报道.同一时候,央视也报道了眼下包 ...

  6. php读取图片成二进制流输出

    header( "Content-type: image/jpeg");$PSize = filesize('1.jpg');$picturedata = fread(fopen( ...

  7. mysql与java的之间的连接

    package cn.hncu; //注意,以下都是sun公司的接口(类)---这样以后换成Oracle等其它数据库,代码不用动import java.sql.Connection;import ja ...

  8. Ⅱ.spring的点点滴滴--对象

    承接上文 对象的各种实例化 .net篇(环境为vs2012+Spring.Core.dll) 修改原来的PersonDao对象为 public class PersonDao : IPersonDao ...

  9. C++ (P199—P211)多态 虚函数 抽象类

    在介绍多态之前,先回忆:赋值兼容原则.虚基类.二义性.派生类如何给基类赋值等知识. 在赋值兼容原则中:父类对象的指针赋给基类的指针或者父类的对象赋给基类的引用,可以通过强转基类的指针或者引用变为父类的 ...

  10. Fragment+Activity传递数据

    自己经常使用的知识点,每次到要用的时候都还要再查一次才能懂得使用,终于体会到总结的必要性了. Activity传递数据给Fragment Bundle bundle_fragment=new Bund ...