ArrayList和LinkedList的區別

List代表一種線性表的數據結構,ArrayList則是一種順序存儲的線性表。ArrayList底層採用數組來保存每個集合元素,LinkedList則是一種鏈式存儲的線性表。其本質上就是一個雙向鏈表,但它不僅實現了List接口,還實現了Deque接口。也就是說LinkedList既可以當成雙向鏈表使用,也可以當成隊列使用,還可以當成棧來使用(Deque代表雙端隊列,即具有隊列的特徵,也具有棧的特徵)。

ArrayList底層採用一個elementData數組來保存所有的集合元素,因此ArrayList在插入元素時需要完成下面兩件事情。

  • 保證ArrayList底層封裝的數組長度大於集合元素的個數;
  • 將插入位置之後的所有數組元素“整體搬家”,向後移動一“格”。

反過來,當刪除ArrayList集合中指定位置的元素時,程序也要進行”整體搬家”,而且還需將被刪索引處的數組元素賦爲null。下面是ArrayList集合的remove(int index)方法的源代碼。

public E remove(int index) {
    //如果index是大於或等於size,拋出異常
    RangeCheck(index);

    modCount++;
    //index索引處的元素
    E oldValue = (E) elementData[index];
    //計算需要"整體搬家"的元素個數
    int numMoved = size - index - 1;
    //當numMoved大於0時,開始搬家
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                 numMoved);
    //釋放被刪除的元素
    elementData[--size] = null; // Let gc do its work

    return oldValue;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

對於ArrayList集合而言,當程序向ArrayList中添加、刪除集合元素時,ArrayList底層都需要對數組進行”整體搬家” 因此性能非常差。

但如果程序調用get(int index)方法來取出ArrayList集合中的元素時,性能和數組幾乎相同–非常快。下面是ArrayList集合get(int index)方法的源代碼。

public E get(int index) {
    RangeCheck(index);
    //取出index索引處的元素
    return (E) elementData[index];
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

LinkedList本質上就是一個雙向鏈表,因此它使用如下內部類來保存每個集合元素。

private static class Entry<E> {
    //集合元素
    E element;
    //保存指向下一個鏈表節點的引用
    Entry<E> next;
    //保存指向上一個鏈表節點的引用
    Entry<E> previous;
    //普通構造器
    Entry(E element, Entry<E> next, Entry<E> previous) {
        this.element = element;
        this.next = next;
        this.previous = previous;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

從上面代碼可以看出,一個Entry對象代表雙向鏈表的一個節點,該對象中next變量指向下一個節點,previous則指向上一個節點。

由於LinkedList採用雙向鏈表來保存集合元素,因此它在添加集合元素時候,只要對鏈表進行如圖所示的操作即可添加一個新節點。

這裏寫圖片描述

下面是LinkedList添加節點的源代碼

//在指定位置插入新節點
public void add(int index, E element) {
    //如果index==size,直接在header之前插入新節點
    //否則,在index索引處的節點之前插入新節點
    addBefore(element, (index==size ? header : entry(index)));
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

從上面代碼看出,由於LinkedList本質上就是一個雙向鏈表,因此它可以非常方便地在指定節點之前插入新節點,LinkedList在指定位置添加新節點也是通過這種方式來實現的。

上面add(int index, E element)方法實現中用到了以下兩個方法。

  • entry(int idnex): 搜索指定索引處的元素。
  • addBefore(E element, Entry ref): 在ref節點之前插入element新節點

entry(int index)實際上就是get(int index)方法的底層實現。對於ArrayList而言,由於它底層採用數組來保存集合元素,因此可以直接根據數組索引取出index位置的元素;但對於LinkedList就比較麻煩了,LinkedList必須一個元素一個元素地搜索,直到找到第index個元素爲止。

下面是entry(int index)方法的源代碼

//獲取指定索引處的節點
private Entry<E> entry(int index) {
    //如果index越界,拋出異常
    if (index < 0 || index >= size)
        throw new IndexOutOfBoundsException("Index: "+index+
                                            ", Size: "+size);
    //從鏈表的頭開始搜索
    Entry<E> e = header;
    //如果index小於size/2
    if (index < (size >> 1)) {
        //從鏈表的頭端開始搜索
        for (int i = 0; i <= index; i++)
            e = e.next;
    }
    //如果index大於size/2
    else {
        //從鏈表的尾端開始搜索
        for (int i = size; i > index; i--)
            e = e.previous;
    }
    return e;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

上面entry(int index)方法就是一個元素一個元素的找到index索引處的元素,只是由於LinkedList是一個雙向鏈表,因此程序先根據index的值判斷它到底離鏈表頭端近(當index

public E get(int index) {
    return entry(index).element;
}
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

無論如何,LinkedList爲了獲取指定索引處的元素都是比較麻煩的,系統開銷也會比較大。但單純的插入操作就比較簡單了,只要修改幾個節點裏的previous、next引用的值。下面是addBefore(E element,Entry ref)方法的源代碼。

//值指定節點(entry)之前添加一個新節點
private Entry<E> addBefore(E e, Entry<E> entry) {
    //創建新節點,新節點的下一個節點指向entry,上一個節點指向entry的上一個節點
    Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
    //讓entry的上一個節點向後指向新節點
    newEntry.previous.next = newEntry;
    //讓entry向前指向新節點
    newEntry.next.previous = newEntry;
    size++;
    modCount++;
    return newEntry;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

如果只是單純地添加某個節點,LinkedList的性能會非常好,可惜如果需要向指定索引處添加節點,LinkedList必須先找到指定索引處的節點 – 這個搜索過程的系統開銷並不小,因此LinkedList的add(int index,E element)方法的性能並不是特別好。如果希望從LinkedList中刪除一個節點,底層雙向鏈表可按如圖所示操作。

這裏寫圖片描述

類似地,LinkedList爲了實現remove(int index)方法 – 刪除指定索引處的節點,也必須先通過entry(int index)方法找到index索引處的節點,然後修改它前一個節點的next引用以及後一個節點的previous引用。下面是LinkedList的remove(int index)方法的源代碼

public E remove(int index) {
    //搜索到index索引處的節點,然後刪除該節點
    return remove(entry(index));
}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

從上面代碼可以看出,程序先調用entry(index)搜索到index索引處的節點,然後調用remove(Entry entry)方法刪除指定節點。刪除entry節點時只需修改entry前一個節點next引用,修改entry後一個節點的previous引用。下面是該方法的源代碼。

private E remove(Entry<E> e) {
    if (e == header)
        throw new NoSuchElementException();

    //先保存e節點的元素
        E result = e.element;
    e.previous.next = e.next;
    e.next.previous = e.previous;
    //將被刪除的元素的兩個引用、元素都賦爲null、以便垃圾回收
        e.next = e.previous = null;
        e.element = null;
    size--;
    modCount++;
        return result;
}
轉載出處: http://blog.csdn.net/itmyhome1990/article/details/76135200


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章