ArrayList哪一種循環效率更好你真的清楚嗎

ArrayList簡介

聲明:如下內容都是基於jdk1.8的
  • ArrayList 是一個數組隊列,至關於 動態數組。與Java中的數組相比,它的容量能動態增加。它繼承於AbstractList,實現了List, RandomAccess, Cloneable, java.io.Serializable這些接口。

看過ArrayList 源碼的同窗有沒有注意過有這麼一個細節:<font color='blue'>爲何ArrayList實現了RandomAccess這個接口,可是 LinkedList卻沒有實現這個接口?這是一個空接口,裏面沒有任何的方法,有什麼做用呢?</font>
答案: RandomAccess 是一個標誌接口,代表實現這個這個接口的 List 集合是支持快速隨機訪問的。也就是說,實現了這個接口的集合是支持 快速隨機訪問 策略的。而LinkedList是不能實現隨機訪問的。html

ArrayList數據結構

ArrayList包含了兩個重要的對象:elementData 和 size。java

  • elementData 是"Object[]類型的數組",它保存了添加到ArrayList中的元素。實際上,elementData是個<font color=blue>動態數組</font>。
    那是否是有人就會問既然ArrayList本質是數組,那爲啥它的長度能夠改變?
    <font color=red>首先,數組的確長度不能改變。不過,ArrayList內部有一系列騷操做,大概就是它每次以爲長度不夠就會 建立一個新數組,這個新數組的容量比原來多出50%,把原來的數組copy過來,而後把之前的數組銷燬掉。</font>
  • size 則是動態數組的實際大小。

ArrayList遍歷方式

  • 第1種,普通for循環隨機訪問,經過索引值去遍歷。
// 隨機訪問
     List<String> list = new ArrayList<>();
     int size = list.size();
     for (int i = 0; i < size; i++) {
         value = list.get(i);
     }
  • 第2種,經過迭代器遍歷。即經過Iterator去遍歷。
// 加強for循環
    for (String s : list) {
        value = s;
    }
  • 第3種,加強for循環遍歷。
// 迭代器遍歷
    Iterator<String> iter = list.iterator();
    while (iter.hasNext()) {
        value = iter.next();
    }
  • 第4種 forEach + lambda 循環遍歷
list.forEach(p -> {
                p.hashCode();
            });

既然有4種遍歷,那咱們看看哪一種遍歷效率下面咱們經過一個實驗來看下這四種循環的耗時吧:
測試代碼數組

/**
 * @Date: 2020/4/23 
 * @Description:
 */
public class ArrayListTest {
    public static void main(String[] args) {
        // 數據預熱
       /* List<String> testList = createTestList(10);
        testForEach(testList);
        testFor(testList);
        testRandFor(10,testList);*/
        List<Integer> integers = Arrays.asList(10, 50, 100,500,1000, 10000, 50000, 100000, 5000000, 10000000,30000000);
        for (Integer i : integers) {
            testRand(i);
        }

    }

    private static void testRand(int size) {
        System.out.println("-----------次數:" + size + "------------");
        List<String> list = createTestList(size);
        // 隨機訪問經過索引值去遍歷。
        long time1 = System.nanoTime();
        testRandFor(size, list);
        long time2 = System.nanoTime();
        // 加強for循環
        testFor(list);
        long time3 = System.nanoTime();
        // 迭代器遍歷
        testIterator(list);
        long time4 = System.nanoTime();
        // forEach + lambda
        testForEach(list);
        long time5 = System.nanoTime();

        System.out.println("隨機訪問\t\t" + (time2 - time1) / 1000 + " ms");
        System.out.println("加強for遍歷\t\t" + (time3 - time2) / 1000 + " ms");
        System.out.println("迭代器遍歷\t\t" + (time4 - time3) / 1000 + " ms");
        System.out.println("forEach遍歷\t\t" + (time5 - time4) / 1000 + " ms");
        System.out.println();
    }

    private static void testRandFor(int size, List<String> list) {
        for (int i = 0; i < size; i++) {
            list.get(i).hashCode();
        }
    }

    private static void testFor(List<String> list) {
        for (String s : list) {
            s.hashCode();
        }
    }

    private static void testIterator(List<String> list) {
        Iterator<String> iter = list.iterator();
        while (iter.hasNext()) {
            iter.next().hashCode();
        }
    }

    private static void testForEach(List<String> list) {
        list.forEach(p -> {
            p.hashCode();
        });
    }

    public static List<String> createTestList(int size) {
        List<String> list = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            list.add(UUID.randomUUID().toString());
        }
        return list;
    }
}

測試數據結果以下:數據結構

-----------次數:10------------
隨機訪問        8 ms
加強for遍歷        5 ms
迭代器遍歷        2 ms
forEach遍歷        40358 ms

-----------次數:50------------
隨機訪問        4 ms
加強for遍歷        8 ms
迭代器遍歷        7 ms
forEach遍歷        5 ms

-----------次數:100------------
隨機訪問        13 ms
加強for遍歷        18 ms
迭代器遍歷        14 ms
forEach遍歷        10 ms

-----------次數:500------------
隨機訪問        54 ms
加強for遍歷        28 ms
迭代器遍歷        24 ms
forEach遍歷        57 ms

-----------次數:1000------------
隨機訪問        106 ms
加強for遍歷        56 ms
迭代器遍歷        50 ms
forEach遍歷        37 ms

-----------次數:10000------------
隨機訪問        1192 ms
加強for遍歷        892 ms
迭代器遍歷        861 ms
forEach遍歷        594 ms

-----------次數:50000------------
隨機訪問        3651 ms
加強for遍歷        2908 ms
迭代器遍歷        2563 ms
forEach遍歷        2712 ms

-----------次數:100000------------
隨機訪問        10693 ms
加強for遍歷        5273 ms
迭代器遍歷        9294 ms
forEach遍歷        3638 ms

-----------次數:5000000------------
隨機訪問        238922 ms
加強for遍歷        29914 ms
迭代器遍歷        30533 ms
forEach遍歷        28016 ms

-----------次數:10000000------------
隨機訪問        431047 ms
加強for遍歷        47151 ms
迭代器遍歷        46371 ms
forEach遍歷        38943 ms

-----------次數:30000000------------
隨機訪問        1163935 ms
加強for遍歷        137710 ms
迭代器遍歷        139211 ms
forEach遍歷        129960 ms
  • 結論:<font color="blue">若是數據量比較少的話貌似四種循環耗時都差很少,可是隨着數據量的增加會發現foreach的效率是最好的。</font>

可是從上面咱們會發現一個奇怪的現象,第一次循環的時候<font color=red>forEach遍歷</font>的時間是最長的儘管數據量很是少也會這樣。可是後面的耗時就正常了。若是放開測試裏面的預熱代碼,每次跑出來的耗時也是正常的。dom

  • 這個結論貌似和網上的一些結論有點偏差:若是你在百度上搜索java for foreach java8 等關鍵詞會出現不少的搜索結果,好比這幾個循環效率的對比。而且不少博主的結論是java8的foreach循環是真的菜,效率不是差的一點點!!!慎用,之類的。

若java8的foreach效率如此低下,爲什麼還要推出?難道jdk的開發人員不會優化一下?帶着這個思考,我仔細看了「已往之不諫」的博主最後爲java8 正名的博客,寫的不錯,測試也很充分(說實話,沒有仔細的閱讀)可是結論很明顯。java8勝了。做者爲了證實java8不是吃素的,確實下了很多功夫。最後的最後,做者提到了,「java8的foreach預熱是jvm級別的,須要預熱。」原文連接感興趣的能夠去看下。jvm

ArrayList刪除數據

雖然有四種遍歷方式,可是可以正確刪除數據的方式只有兩種測試

  • 第1種經過迭代器進行刪除。這種方式的話,也是《阿里代碼規約》所推薦的。

在這裏插入圖片描述

Iterator<String> iter = list.iterator();
        while (iter.hasNext()) {
            iter.next().hashCode();
            iter.remove();
        }
  • 第2種倒序循環刪除
for(int i = list.size()-1;i>=0;i--){
            list.remove(i);
        }

下面再演示下錯誤的刪除操做優化

  • 普通for循環正序刪除,刪除過程當中元素向左移動,不能刪除重複的元素
List<String> list = new ArrayList<>();
        list.add("1");
        list.add("1");
        list.add("2");
        for(int i=0;i<list.size();i++){
           list.remove(i);
        }
        System.out.println(String.join(",",list));

結果輸出:1spa

  • 加強for循環刪除會拋出 java.util.ConcurrentModificationException

ArryList注意點

  • 謹慎使用ArrayList中的subList方法code

    • ArrayList的subList結果不可強轉成ArrayList,不然會拋出ClassCastException 異常,即 java.util.RandomAccessSubList cannot be cast to java.util.ArrayList. 說明:subList 返回的是 ArrayList 的內部類 SubList,並非 ArrayList ,而是 ArrayList 的一個視圖,對於 SubList 子列表的全部操做最終會反映到原列表上。

      List<String> list = new ArrayList<>();
              list.add("1");
              list.add("1");
              list.add("2");
              ArrayList<String> strings =  (ArrayList)list.subList(0, 1);
        
        運行結果:
        Exception in thread "main" java.lang.ClassCastException: java.util.ArrayList$SubList cannot be cast to java.util.ArrayList
        at com.workit.demo.listener.ArrayListTest.main(ArrayListTest.java:29)
    • 在 subList 場景中,高度注意對原集合元素個數的修改,會致使子列表的遍歷、增長、 刪除均會產ConcurrentModificationException 異常。
List<String> list = new ArrayList<>();
        list.add("1");
        list.add("1");
        list.add("2");
        List<String> subList =  list.subList(0, 1);
        // 對原List增長一個值
        list.add("10");
        subList.add("11"); // 這一行會報 java.util.ConcurrentModificationException
  • 初始化List的時候儘可能指定它的容量大小。(儘可能減小擴容次數)

結束

  • 因爲本身才疏學淺,不免會有紕漏,假如你發現了錯誤的地方,還望留言給我指出來,我會對其加以修正。
  • 若是你以爲文章還不錯,你的轉發、分享、讚揚、點贊、留言就是對我最大的鼓勵。
  • 感謝您的閱讀,十分歡迎並感謝您的關注。

在這裏插入圖片描述

相關文章
相關標籤/搜索