JAVA容器之List

List的實現類主要是ArrayList和LinkedList,兩個主要的差異是ArrayList是經過數組實現的,但LinkedList是經過鏈表實現。
能夠想象,ArrayList在隨機訪問效率上遠高於LinkedList,由於LinkedList訪問一個元素必須從頭節點開始依次訪問知道找到目標節點,因此時間複雜度爲O(n),而ArrayList的隨機訪問複雜度幾乎是O(1)。
但頻繁進行插入刪除數據的時候,LinkedList的效率遠高於ArrayList,由於LinedList只須要改變指針(引用)的內容,時間複雜度爲O(1),而ArrayList添加與刪除須要大量移動元素,時間複雜度爲O(n)。java

遍歷效率分析

public class ListTest {
    List<Integer> array = new ArrayList<Integer>();
    List<Integer> link = new LinkedList<Integer>();
    private final int size;
    private long start, end;

    public ListTest(int size) {
        this.size = size;
        for (int i = 0; i < size; i++) {
            int t = (int) (Math.random() * 1000);
            array.add(t);
            link.add(t);
        }
    }

    public void ArrayIteratorTraversal() {
        Iterator<Integer> i = array.iterator();
        int t;
        start = System.currentTimeMillis();
        while (i.hasNext()) {
            t = i.next();
        }
        end = System.currentTimeMillis();
        System.out.println("ArrayList使用Iterator遍歷" + size + "個數據花了"
                + (end - start) + "ms");
    }

    public void LinkIteratorTraversal() {
        Iterator<Integer> i = link.iterator();
        int t;
        start = System.currentTimeMillis();
        while (i.hasNext()) {
            t = i.next();
        }
        end = System.currentTimeMillis();
        System.out.println("LinkedList使用Iterator遍歷" + size + "個數據花了"
                + (end - start) + "ms");
    }

    public void ArrayListIteratorTraversal() {
        ListIterator<Integer> i = array.listIterator();
        int t;
        start = System.currentTimeMillis();
        while (i.hasNext()) {
            t = i.next();
        }
        end = System.currentTimeMillis();
        System.out.println("ArrayList使用ListIterator遍歷" + size + "個數據花了"
                + (end - start) + "ms");
    }

    public void LinkListIteratorTraversal() {
        ListIterator<Integer> i = link.listIterator();
        int t;
        start = System.currentTimeMillis();
        while (i.hasNext()) {
            t = i.next();
        }
        end = System.currentTimeMillis();
        System.out.println("LinkedList使用ListIterator遍歷" + size + "個數據花了"
                + (end - start) + "ms");
    }

    public void ArrayCycleTraversal() {
        int t;
        start = System.currentTimeMillis();
        for (int i = 0; i < size; i++)
            t = array.get(i);
        end = System.currentTimeMillis();
        System.out.println("ArrayList使用循環遍歷" + size + "個數據花了"
                + (end - start) + "ms");
    }

    public void LinkCycleTraversal() {
        int t;
        start = System.currentTimeMillis();
        for (int i = 0; i < size; i++)
            t = link.get(i);
        end = System.currentTimeMillis();
        System.out.println("LinkedList使用循環遍歷" + size + "個數據花了"
                + (end - start) + "ms");
    }

    public static void main(String[] args) {
        ListTest t = new ListTest(10000000);
        System.out.println("*******開始*******");
        t.ArrayIteratorTraversal();
        t.LinkIteratorTraversal();
        t.ArrayListIteratorTraversal();
        t.LinkListIteratorTraversal();
        t.ArrayCycleTraversal();
// t.LinkCycleTraversal();
        System.out.println("*******結束*******");
    }
}

其中分別用了Iterator,ListIterator和循環來訪問ArrayList和LinkedList,輸出結果爲
//輸出結果
*開始*
ArrayList使用Iterator遍歷100000個數據花了9ms
LinkedList使用Iterator遍歷100000個數據花了9ms
ArrayList使用ListIterator遍歷100000個數據花了8ms
LinkedList使用ListIterator遍歷100000個數據花了9ms
ArrayList使用循環遍歷100000個數據花了6ms
LinkedList使用循環遍歷100000個數據花了8049ms
*結束*
//輸出結果數組

使用迭代器時,ArrayList比LinkedList要快上很多,屢次測試大約是三倍的樣子,Iterator和ListIterator遍歷一樣的容器效率差很少。可是經過循環遍歷,LinkedList跟ArrayList差了三個數量級,其實用循環遍歷,看似是順序遍歷List,可是,經過List.get()的方式過的元素值,實際上是經過隨機訪問來實現的,因此也從這個例子能夠證實上面所說LinkedList的隨機訪問效率是很是差的。
因爲數據再增加,最後一項LinkedList循環遍歷的測試等待時間很是長,因此把這一項測試去掉,你們知道這個效率很是差就行了。咱們把測試數據規模擴大到10billion:
//輸出結果
*開始*
ArrayList使用Iterator遍歷10000000個數據花了37ms
LinkedList使用Iterator遍歷10000000個數據花了121ms
ArrayList使用ListIterator遍歷10000000個數據花了38ms
LinkedList使用ListIterator遍歷10000000個數據花了117ms
ArrayList使用循環遍歷10000000個數據花了30ms
*結束*
//輸出結果
擴大數據規模後發現有一點是跟上一次測試結果相同的,就是ArrayList的效率明顯比LinkedList要高一些,並且另外發現一個問題,用循環遍歷ArrayList比用迭代器也要快一些。這是爲何呢?
一、 爲何ArrayList循環遍歷比迭代器遍歷快?markdown

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];
}

這是ArrayList中一個繼承了Iterator的內部類的next()方法,看了源代碼就能夠發現其實ArrayList的Iterator就是直接訪問ArryList中用於存放數據的一個數組,可是多了一些檢查工做。並且,除此以外,遍歷時每次iterator.hasNext()也須要額外的時間開銷,因此這樣遍歷天然會比較滿一些。
二、 爲何用迭代器遍歷ArrayList比LinkedList快一些呢?
這個問題從源碼角度還真很差說,搜了一些資料,發現你們對這個效率緣由答案都不是特別明確,依稀看到有人的解釋我以爲仍是蠻有道理的,就是ArrayList的數據結構內存分配是連續的,而LinkedList是不連續的,訪問下一個元素的時候須要經過地址去查找,因此有了額外的時間開銷。數據結構

操做Arrays.asList()

有這麼一使用Arrays.asList的例子:dom

List<Integer>list=Arrays.asList(5,6,8,2,45,1,2,8,5,125,6,8,5);
        list.add(5);

經過Arrays.asList方法把一串數字轉化成list,而後調用list中的add方法添加元素。可是運行會發現,編譯器拋出了一個java.lang.UnsupportedOperationException異常,問題就在add方法,其實除了add方法,remove,retain等對list元素個數會產生變化的方法都是會報這個異常的,究其緣由,看一下Arrays.asList的源碼
乍一看,發現Arrays.asList返回的就是一個ArrayList對象,可是仔細看發現這個ArrayList並不是java.util.ArrayList,而是Arrays的一個內部類,並且沒有實現剛纔說的add那些方法,而是直接繼承了AbstractList,而後AbstractList並無實現add等方法,只是直接拋出了UnsupportedOperationException異常,因此纔會有上述代碼發生的那些狀況。測試

正確的使用:大數據

List<Integer> list = new ArrayList<Integer>(Arrays.asList(5, 6, 8, 2,
                45, 1, 2, 8, 5, 125, 6, 8, 5));
        list.add(5);

總結

對於LinekdList和ArrayList的差別,主要就差異在隨機訪問與修改,因此建議以下:
一、 若是須要大量刪除與添加元素,使用LinkedList
二、 若是須要大量隨機訪問元素,使用ArrayList
三、 若是須要常常遍歷,二者都可,ArrayList效率略高,可是若是選用LinedList,在遍歷時儘可能使用迭代器,不然至關於隨機存取,效率很是低。this

相關文章
相關標籤/搜索