Java集合原始碼學習(3)LinkedList

快樂的博格巴發表於2018-10-02

ArrayList,陣列是順序儲存結構,儲存區間是連續的,佔用記憶體嚴重,故空間複雜的很大。但陣列的二分查詢時間複雜度小,為O(1),陣列的特點是定址容易,插入和刪除困難。 LinkedList使用連結串列作為儲存結構,連結串列是線性儲存結構,在記憶體上不是連續的一段空間,佔用記憶體比較寬鬆,故空間複雜度很小,但時間複雜度很大,達O(N),連結串列的特點是定址困難,插入和刪除容易。

在JDK1.7之前,LinkedList是採用雙向環形連結串列來實現的,在1.7及之後,Oracle將LinkedList做了優化,將環形連結串列改成了線性連結串列。

public class LinkedList<E>
    extends AbstractSequentialList<E>
     implements List<E>, Deque<E>, Cloneable, java.io.Serializable
複製程式碼

LinkedList繼承了AbstractSequentialList,實現了List,Deque,Cloneable,Serializable 介面

(1)繼承和實現

繼承AbstractSequentialList類,提供了相關的新增、刪除、修改、遍歷等功能。
實現List介面,提供了相關的新增、刪除、修改、遍歷等功能。 實現 Deque 介面,即能將LinkedList當作雙端佇列使用,可以用做佇列或者棧。 實現了Cloneable介面,即覆蓋了函式clone(),能被克隆。 實現java.io.Serializable介面,LinkedList支援序列化,能通過序列化傳輸。

(2)執行緒安全

LinkedList是非同步的,即執行緒不安全,如果有多個執行緒同時訪問LinkedList,可能會丟擲ConcurrentModificationException異常。

final void checkForComodification() {
        if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    }
複製程式碼

(3)節點Node結構:

private static class Node<E> {
    E item;//元素值
    Node<E> next;//後置節點
    Node<E> prev;//前置節點

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}
複製程式碼

(4)addAll方法:

//addAll ,在尾部批量增加
複製程式碼

public boolean addAll(Collection<? extends E> c) { return addAll(size, c);//以size為插入下標,插入集合c中所有元素 } //以index為插入下標,插入集合c中所有元素 public boolean addAll(int index, Collection<? extends E> c) { checkPositionIndex(index);//檢查越界 [0,size] 閉區間

Object[] a = c.toArray();//拿到目標集合陣列
int numNew = a.length;//新增元素的數量
if (numNew == 0)//如果新增元素數量為0,則不增加,並返回false
    return false;

Node<E> pred, succ;  //index節點的前置節點,後置節點
if (index == size) { //在連結串列尾部追加資料
    succ = null;  //size節點(隊尾)的後置節點一定是null
    pred = last;//前置節點是隊尾
} else {
    succ = node(index);//取出index節點,作為後置節點
    pred = succ.prev; //前置節點是,index節點的前一個節點
}
//連結串列批量增加,是靠for迴圈遍歷原陣列,依次執行插入節點操作。對比ArrayList是通過System.arraycopy完成批量增加的
for (Object o : a) {//遍歷要新增的節點。
    @SuppressWarnings("unchecked") E e = (E) o;
    Node<E> newNode = new Node<>(pred, e, null);//以前置節點 和 元素值e,構建new一個新節點,
    if (pred == null) //如果前置節點是空,說明是頭結點
        first = newNode;
    else//否則 前置節點的後置節點設定問新節點
        pred.next = newNode;
    pred = newNode;//步進,當前的節點為前置節點了,為下次新增節點做準備
}

if (succ == null) {//迴圈結束後,判斷,如果後置節點是null。 說明此時是在隊尾append的。
    last = pred; //則設定尾節點
} else {
    pred.next = succ; // 否則是在隊中插入的節點 ,更新前置節點 後置節點
    succ.prev = pred; //更新後置節點的前置節點
}

size += numNew;  // 修改數量size
modCount++;  //修改modCount
return true;
}
//根據index 查詢出Node,
Node<E> node(int index) {
    // assert isElementIndex(index);
//通過下標獲取某個node 的時候,(增、查 ),會根據index處於前半段還是後半段 進行一個折半,以提升查詢效率
    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

private void checkPositionIndex(int index) {
    if (!isPositionIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
    return index >= 0 && index <= size;  //插入時的檢查,下標可以是size [0,size]
}
複製程式碼

在構造方法中呼叫addAll方法,相當於是向一個空連結串列中新增集合c中的元素。 如果是在已有元素的連結串列中呼叫addAll方法來新增元素的話,就需要判斷指定的新增位置index是否越界,如果越界會丟擲異常;如果沒有越界,根據新增的位置index,斷開連結串列中index位置的節點前後的引用,加入新元素,重新連上斷開位置的前後節點的引用。過程如下圖:(此處用了xiaoyanger的圖)

Java集合原始碼學習(3)LinkedList

(5)toArray()方法:

    public Object[] toArray() {
    //new 一個新陣列 然後遍歷連結串列,將每個元素存在陣列裡,返回
    Object[] result = new Object[size];
    int i = 0;
    for (Node<E> x = first; x != null; x = x.next)
        result[i++] = x.item;
    return result;
    }
複製程式碼

這個方法的實現很簡單。

(6)總結:

LinkedList 是雙向列表。

連結串列批量增加,是靠for迴圈遍歷原陣列,依次執行插入節點操作。對比ArrayList是通過System.arraycopy完成批量增加的。增加一定會修改modCount。 通過下標獲取某個node 的時候,(add select),會根據index處於前半段還是後半段 進行一個折半,以提升查詢效率 刪也一定會修改modCount。 按下標刪,也是先根據index找到Node,然後去連結串列上unlink掉這個Node。 按元素刪,會先去遍歷連結串列尋找是否有該Node,如果有,去連結串列上unlink掉這個Node。 改也是先根據index找到Node,然後替換值。改不修改modCount。 查本身就是根據index找到Node。 所以它的CRUD(Create,Retrieve,Update,Delete)操作裡,都涉及到根據index去找到Node的操作。

相關文章