LinkedList源碼分析(JDK8)

一.LinkedList的關係依賴

public class LinkedList<E>
        extends AbstractSequentialList<E>
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable {

繼承了一個類,實現了四個接口

AbstractSequentialList抽象類,是AbstractList的子類。此類提供了List接口的基本實現,以最大程度地減少由順序訪問數據存儲(例如鏈表)支持的實現此接口所需的工作。對於隨機訪問數據(例如數組),應優先使用AbstractList。

List接口,有序集合。允許重複的元素,也允許多個空元素,擁有List集合的常用方法。

Deque接口,是Queue的子接口,支持在兩端插入和刪除元素的線性集合。

Cloneable接口和Serializable接口都是標記接口,言外之意這兩個接口沒有內容。分別進行拷貝和序列化,這裏不做詳細概述。

注:不過需要注意的是,要使用Object的clone()方法必須實現Cloneable接口,否則會報java.lang.CloneNotSupportedException的異常。

在這裏插入圖片描述
類圖

二.屬性

/**
 * 集合元素的個數
 */
transient int size = 0;

/**
 * 指向第一個節點的指針
 * Invariant: (first == null && last == null) ||
 *            (first.prev == null && first.item != null)
 */
transient Node<E> first;

/**
 * 指向最後一個節點的指針
 * Invariant: (first == null && last == null) ||
 *            (last.next == null && last.item != null)
 */
transient Node<E> last;

三.構造方法

/**
 * 無參構造
 */
public LinkedList() {
}

 /**
 * 構造一個包含特定集合的list,其元素按照迭代器的順序返回。
 *
 * @param c 集合的元素都要放到list中
 * @throws NullPointerException 如果這個集合爲空,拋出空指針異常
 */
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

四.代碼分析

1.節點類

/**
 * 節點類
 *
 * @param <E>
 */
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;
    }
}

三個屬性,分別是前驅節點(指針)的信息,元素,後繼節點(指針)的信息。

2.添加元素

(1).添加元素

/**
 * 將元素插入到list尾部
 * <p>
 * 此方法與addLast方法調用的是一個方法
 */
@Override
public boolean add(E e) {
    linkLast(e);
    return true;
}

LinkedList默認將元素添加到列表尾部,所以add方法與addLast方法的作用是一樣的。下面來看一下linkLast方法。

/**
 * 在鏈表尾部添加數據
 */
private void linkLast(E e) {
    //獲取當前尾部節點
    final Node<E> l = last;
    //聲明新節點
    final Node<E> newNode = new Node<>(l, e, null);
    //使新節點成爲新的尾部節點
    last = newNode;
    //如果l爲空,那麼是空鏈表。新節點即是鏈表的頭部節點,也是鏈表的尾部節點
    if (l == null)
    {
        first = newNode;
    } else
    //否則,將新節點指向之前尾部元素的後繼節點
    {
        l.next = newNode;
    }
    //元素個數和鏈表修改次數進行計數
    size++;
    modCount++;
}

因爲想在鏈表的尾部添加新元素,所以需要先拿到尾部節點的信息,存在l中。然後創建要添加的元素的節點,其中前驅節點就是剛纔的l,元素爲e,後繼節點爲null,因爲在鏈表的尾部。接着把新節點變成鏈表的尾部元素。剩下最後一個步驟,將二者進行關聯。判斷之前的尾部節點是否爲空,如果爲空那麼新節點的元素是空鏈表的唯一元素,否則,將新節點與之前的尾部節點進行關聯即可。隨後更新元素個數和鏈表修改的次數。

(2).在鏈表頭部插入新元素

 /**
 * 將元素插入到鏈表頭部
 */
@Override
public void addFirst(E e) {
    linkFirst(e);
}

linkFirst具體內容如下:

/**
 * 在鏈表頭部添加數據
 */
private void linkFirst(E e) {
    //獲取當前頭部節點
    final Node<E> f = first;
    //聲明新節點
    final Node<E> newNode = new Node<>(null, e, f);
    使新節點成爲新的頭部節點
    first = newNode;
    //判空
    if (f == null)
    {
        last = newNode;
    } else
    {
        f.prev = newNode;
    }
    //計數
    size++;
    modCount++;
}

與上面的添加元素的方法類似

(3).在鏈表的特定位置插入元素

/**
 * 在鏈表的特定位置插入元素
 * 之前該位置的元素及後面的元素均要右移
 *
 * @param index   指定鏈表的位置
 * @param element 元素
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
@Override
public void add(int index, E element) {

    //校驗索引位置是否合法,即index >= 0 && index <= size。不合法會報出索引越界的異常。
    checkPositionIndex(index);
    //如果索引位置爲鏈表的size,那麼添加的位置便是鏈表尾部
    if (index == size)
    {
        linkLast(element);
    } else
    //在非空節點succ之前插入新節點
    {
        linkBefore(element, node(index));
    }
}

在鏈表的指定位置添加元素,首先需要判斷索引的位置是否合法,即在0和size之間,否則報出越界的異常。然後判斷索引位置是否爲size,如果爲size,只需調用linkLast方法,在鏈表尾部添加新節點即可;如果不爲size,還需調用linkBefore方法。下面來看一下linkBefore方法。

/**
 * 在非空節點succ之前插入新元素
 */
void linkBefore(E e, Node<E> succ) {
    //獲取succ的前驅節點
    final Node<E> pred = succ.prev;
    //聲明新節點
    final Node<E> newNode = new Node<>(pred, e, succ);
    //使新節點成爲succ節點的前驅節點
    succ.prev = newNode;
    //判空
    if (pred == null)
    {
        first = newNode;
    } else
    {
        pred.next = newNode;
    }
    //更新計數
    size++;
    modCount++;
}

這幾個添加方法本質上是一樣的,理解其一,其他的也就都懂了。

(4).將集合作爲參數,添加到鏈表中

/**
 *
 * 內容有些深奧,尚未消化
 *
 * @param c 集合
 * @return {@code true} if this list changed as a result of the call
 * @throws NullPointerException 如果集合爲空,空指針異常
 */
@Override
public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}

3.刪除元素

(1).刪除元素

默認刪除鏈表的頭結點,與removeFirst方法作用一樣。

/**
 * 刪除鏈表頭元素
 *
 * @return 鏈表頭元素
 * @throws NoSuchElementException 如果鏈表爲空
 * @since 1.5
 */
@Override
public E remove() {
    return removeFirst();
}

不進行傳參的remove方法,默認刪除鏈表的頭結點,不過需要進行向上轉型。ArrayList的remove方法必須傳參。

/**
 * 從列表中刪除並返回第一個元素
 *
 * @return 被刪除的節點
 * @throws NoSuchElementException 如果鏈表爲空
 */
@Override
public E removeFirst() {
    //將鏈表的頭指針傳給f
    final Node<E> f = first;
    //如果節點爲空
    if (f == null) {
        throw new NoSuchElementException();
    }
    return unlinkFirst(f);
}

unlinkFirst方法具體內容如下:

/**
 * 刪除非空頭結點
 */
private E unlinkFirst(Node<E> f) {
    // 確定f爲非空頭結點
    //獲取被刪除頭結點的元素
    final E element = f.item;
    //獲取頭結點的下一個節點
    final Node<E> next = f.next;
    //清空該節點的元素和後繼指針
    f.item = null;
    f.next = null;
    //f後繼節點成爲新的頭節點
    first = next;
    //後繼節點爲空,那麼該鏈表是空鏈表
    if (next == null) {
        last = null;
    } else {
    //否則,新的前驅節點置空
        next.prev = null;
    }
    //更新計數
    size--;
    modCount++;
    //返回被刪除的元素
    return element;
}

(2).刪除指定位置的元素

/**
 * 刪除並返回指定位置的元素
 *
 * @param index 索引位置
 * @return 被刪除的元素
 * @throws IndexOutOfBoundsException 索引越界
 */
@Override
public E remove(int index) {
    //是否越界
    checkElementIndex(index);
    return unlink(node(index));
}
/**
 * 刪除非空節點
 */
E unlink(Node<E> x) {
    // assert x != null;
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;
    //前驅節點爲空,刪除的是鏈表頭結點
    if (prev == null) {
        //該節點的後繼節點便成爲新的鏈表頭結點
        first = next;
    } else {
        //前驅節點不爲空,刪除的是鏈表之間元素
        //將被刪除節點的後繼節點與其前驅節點的後繼指針相關聯
        prev.next = next;
        //將被刪除節點的前驅節點置空
        x.prev = null;
    }
    //被刪除節點的後繼節點還未處理

    //後繼節點爲空,刪除的是鏈表尾結點
    if (next == null) {
        //該節點的前驅節點便成爲新的鏈表尾結點
        last = prev;
    } else {
        //該節點的後繼節點不爲空
        //將該節點的後繼節點的前驅指針與前驅節點就行關聯
        next.prev = prev;
        //置空後繼指針
        x.next = null;
    }
    //將被刪除元素置空
    x.item = null;
    //更新計數
    size--;
    modCount++;
    return element;
}

(3).刪除鏈表中第一次出現的元素

/**
 * 刪除鏈表中第一次出現的元素,如果該元素存在。如果該元素不存在,那麼鏈表沒有變化
 *
 * @param o 要被刪除的鏈表元素。
 * @return {@code true} 刪除成功返回真,否則返回假
 */
@Override
public boolean remove(Object o) {
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

判斷要刪除的元素是否爲空,然後遍歷鏈表,找到爲空的元素後調用unlink方法就行刪除;如果不爲空,遍歷列表,如果與鏈表中的元素相等,那麼就行刪除。總的來說都用調用unlink方法,那麼這個判斷有什麼作用呢?我還不是很懂。希望有大佬指點一二。

(4).清空鏈表中的全部元素

/**
 * 清空鏈表的全部元素
 */
@Override
public void clear() {
    //遍歷鏈表
    for (Node<E> x = first; x != null; ) {
        //先獲取x的後繼節點,便於清空x後進行操作
        Node<E> next = x.next;
        //將x節點置空
        x.item = null;
        x.next = null;
        x.prev = null;
        //指向x的後繼節點,繼續遍歷
        x = next;
    }
    //清空鏈表之後,頭結點,尾節點清空
    first = last = null;
    //更新計數
    size = 0;
    modCount++;
}

4.替換鏈表特定位置的元素

/**
 * 替換指定位置的元素
 *
 * @param index   索引
 * @param element 要替換的元素
 * @return 被替換的元素
 * @throws IndexOutOfBoundsException 索引越界
 */
@Override
public E set(int index, E element) {
    //校驗是否越界
    checkElementIndex(index);
    //獲取索引對應位置的節點信息
    Node<E> x = node(index);
    //替換元素
    E oldVal = x.item;
    x.item = element;
    //返回被替換的舊元素
    return oldVal;
}

核心方法是node(index):

/**
 * 返回索引位置的節點
 */
Node<E> node(int index) {
    //>>位運算符,右移1位,即除2
    //如果索引小於鏈表容量的一半
    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;
    }
}

根據JDK8的源碼進行解讀,依照添刪改查的順序簡單的就行了梳理,方便自己的理解和學習。由於部分代碼還不是很明白,後期還會就行更深入的解析。

前路漫漫,編程作伴 --by mirror6

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