集合應用---集合遍歷該如何選擇

相信大家在工作中使用集合已經算是家常便飯了吧,而對集合進行遍歷也算是必不可少的操作了。而對集合進行遍歷也有多種方法,而常用的一般就是for循環和增強for循環(也叫foreach循環)。可能有些人有些迷惑,這兩種循環有什麼區別呢?我們該如何選擇使用呢?用好了系統的性能也能得到一點點的優化哦,接下來就解開神祕的面紗。
先來看一個需求:統計一個省的各科高考平均值,比如數學平均分數。

public static void main(String[] args) {
        //學生數量 80萬
        int stuNum = 80*10000;
        //List集合,記錄所有學生的分數
        List<Integer> scores = new ArrayList<Integer>(stuNum);
        //寫入分數
        for (int i = 0; i < stuNum; i++) {
            scores.add(new Random().nextInt(150));
        }
        //記錄開始計算時間
        long start = System.currentTimeMillis();
        System.out.println("平均分是:"+average(scores));
        System.out.println("執行時間:"+(System.currentTimeMillis()-start)+"ms");
    }

    private static int average(List<Integer> list) {
        int sum = 0;
        //foreach遍歷求和
        for (int i : list) {
            sum +=i;
        }
        return sum/list.size();
    }


平均分:74
執行時間:16ms

如果我們換成第二種:

private static int average(List<Integer> list) {
        int sum = 0;
        //for循環遍歷求和
        for (int i = 0; i < list.size(); i++) {
            sum += list.get(i);
        }
        return sum/list.size(); 
    }
平均分:74
執行時間:0ms

由於第二種執行速度較快在毫秒以內所以顯示0ms,可以看到性能提升了不少,當數據量更大時效果會更明顯。那爲什麼第二種遍歷會有這麼高的性能提升呢?

這是因爲ArrayList數組實現了RandomAccess接口(隨機存取接口),這也就標誌着ArrayList是一個可以隨機存取的列表。在java中,RandomAccess和Cloneable、Serializable一樣,都是標識性接口,不需要任何實現,只是用來表明其實現類具有某種特質的,實現了Cloneable表名可以被拷貝,實現了Serializable接口表明被序列化了,實現了RandomAccess接口表明這個類可以隨機存取,對我們的ArrayList來說也就標誌着其數據元素之間沒有關聯,即兩個位置相鄰的元素之間沒有互相依賴和索引關係,可以隨機訪問和存儲。所以我們說ArrayList是無序的集合。
我們知道,java中的foreach語法是iterator(迭代器)的變形用法,也就是說上面的foreach與下面的代碼等價:

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

迭代器是23個設計模式中的一種,”提供一種方法訪問一個容器對象中的各個元素,同時又無須暴露該對象的內部細節”,也就是說對於ArrayList,需要先創建一個迭代器容器,然後屏蔽內部遍歷細節,對外提供hasNext、nest等方法。而ArrayList實現了RandomAccess接口,已表明元素之間本來沒有關係,可是,爲了使用迭代器就需要強制建立一種互相“知曉”的關係,比如上一個元素可以判斷是否有下一個元素,以及下一個元素是什麼等關係,這也就是通過foreach遍歷耗時的原因。

java爲ArrayList類加上了RandomAccess接口,就是在告訴我們,“嘿,ArrayList是隨機存取的”採用下標方式遍歷列表速度會更快”,那爲什麼不把RandomAccess加到所有的List實現類上呢?

那是因爲有些List實現類不是隨機存取的,而是有序存取的,比如LinkedList類,LinkedList也是一個列表,但它實現了雙向鏈表,每個數據節點中都有三個數據項:前節點的應用(Previous Node)、本節點元素(Node Element)、後繼節點的引用(Next Node),也就是說在LinkedList中的兩個元素本來就是有關聯,所以對於LinkedList來說,使用foreach遍歷是不是效率更高呢?

public static void main(String[] args) {
        //學生數量 80萬
        int stuNum = 80*10000;
        //List集合,記錄所有學生的分數
        List<Integer> scores = new LinkedList<Integer>();
        //寫入分數
        for (int i = 0; i < stuNum; i++) {
            scores.add(new Random().nextInt(150));
        }
        //記錄開始計算時間
        long start = System.currentTimeMillis();
        System.out.println("平均分是:"+average(scores));
        System.out.println("執行時間:"+(System.currentTimeMillis()-start)+"ms");
    }

    private static int average(List<Integer> list) {
        int sum = 0;
        //foreach遍歷求和
        for (int i : list) {
            sum +=i;
        }
        return sum/list.size();
    }

運行結果也是0ms,效率也非常高。我們可以將average方法重構下,以便實現不同的列表採用不同的遍歷方式,代碼如下:

private static int average(List<Integer> list) {
        int sum = 0;
        if(list instanceof RandomAccess){
            for (int i = 0; i < list.size(); i++) {
            sum += list.get(i);
            }
        } else {
            for (int i : list) {
                sum +=i;
            }
        }
        return sum/list.size();
    }
總結:由上可知,實現RandomAccess接口的List集合採用一般的for循環遍歷,而未實現這接口則採用迭代器
發佈了46 篇原創文章 · 獲贊 48 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章