專題三、ArrayList遍歷方式以及效率比較


一、遍歷方式

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的區別


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM