LinkedList源碼解析


 1、簡介
LinkedList類聲明如下:
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
可以發現 LinkedList繼承了 AbstractSequentialList抽象類,而不是像 ArrayList和 Vector那樣實現 AbstractList,實際上,java類庫中只有 LinkedList繼承了這個抽象類,正如其名,它提供了對序列的連續訪問的抽象:
LinkedList的底層是 Deque雙向鏈表,實現了 Deque接口,而 Deque接口繼承於 Queue接口,因此,在java中,如果要實現隊列,一般都使用 LinkedList來實現。
 
Node結點
Node節點 一共有三個屬性:item代表節點值,prev代表節點的前一個節點,next代表節點的后一個節點。
LinkedList中關於鏈表結點的定義如下:
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;
        }
    }
每個結點都有一個前驅和后繼結點,並且在 LinkedList中也定義了兩個變量分別指向鏈表中的第一個和最后一個結點:
transient Node<E> first;
transient Node<E> last;

 

2、添加(add)操作
public boolean add(E e) {
    linkLast(e);
    return true;
}

void linkLast(E e) {
    final Node<E> l = last;//鏈表最后一個節點
    final Node<E> newNode = new Node<>(l, e, null);//建立節點對象,前一個(perv屬性)是last,后一個(next屬性)是null,中間item數據是輸入的參數e
    last = newNode;
    if (l == null)
        first = newNode; //如果最后一個節點 為null表示鏈表為空,則添加數據時將新加的數據作為第一個節點
    else
        l.next = newNode; //如果鏈表不為空則將最后一個鏈表的next屬性指向新添加的節點
    size++; //鏈表長度自增1
    modCount++;
}

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;
    }
}
總結:
LInkedList添加操作時每個新添加的對象都會被放到新建的Node對象中,Node對象中包含加入的對象和前后指針屬性(pred和next,pred和next其實都是Node對象,直接存放的就是當前對象的前一個節點對象和后一個節點對象,最后一個及節點的next值為null),然后將原來最后一個last節點的next指針指向新加入的對象,即可
 
addAll操作:
public boolean addAll(int index, Collection<? extends E> c) {
    checkPositionIndex(index);//判斷index是否越界,越界則拋出異常
    Object[] a = c.toArray();
    int numNew = a.length;//要插入的集合的長度
    if (numNew == 0)
        return false;
    Node<E> pred, succ;//聲明pred和succ兩個Node對象,用於標識要插入元素的前一個節點和最后一個節點
    if (index == size) { //如果size等於原數組長度則表示在結尾添加
        succ = null;
        pred = last;
    } else {
        succ = node(index);//index位置上的Node對象
        pred = succ.prev;
    }
    for (Object o : a) { //遍歷要插入的集合
        @SuppressWarnings("unchecked") E e = (E) o;
        Node<E> newNode = new Node<>(pred, e, null);
        if (pred == null)
            first = newNode; //如果要插入的位置的前一個節點為null表示是第一個節點,則直接將newNode賦給第一個節點
        else
            pred.next = newNode; //將要插入的集合元素節點對象賦給此位置原節點對象的前一個對象的后一個,即更改前一個節點對象的next指針指到新插入的節點上
        pred = newNode;//更改指向后將新節點對象賦給pred作為下次循環中新插入節點的前一個對象節點,依次循環
    }
    //此時pred代表集合元素的插入完后的最后一個節點對象
    if (succ == null) { //結尾添加的話在添加完集合元素后將最后一個集合的節點對象pred作為last
        last = pred;
    } else {
        pred.next = succ;//將集合元素的最后一個節點對象的next指針指向原index位置上的Node對象
        succ.prev = pred;//將原index位置上的pred指針對象指向集合的最后一個對象
    }
    size += numNew;
    modCount++;
    return true;
}

Node<E> node(int index) {
    if (index < (size >> 1)) { //判斷index是否小於size的一半,如果小於則從頭遍歷節點,否則從結尾遍歷節點
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next; //從first第一個節點開始,依次將后一個節點賦給x
        return x; //返回index位置上的Node對象,下同理
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}
總結:
LinkedList在某個位置插入元素是通過將原位置節點的前一個節點的后一個指針指向新插入的元素,然后將原位置的節點的前一個指針指向新元素來實現的,相當於插入元素后后面的元素后移了,但是不是像ArrayList那樣將所有插入位置后面的元素都后移,此處只是改變其前后節點的指向
 
3、刪除操作
public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}

E unlink(Node<E> x) {
    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;//如果next為null表示是最后一個元素,直接將pred節點賦給last
    } else {
        next.prev = prev;
        x.next = null;//將傳入節點的后一個節點置空,使其后一個節點指針不指向任何元素
    }
    x.item = null;//將傳入的節點對象上的對象置空,也就是整個Node對象中的屬性都為空了,后面會被GC回收
    size--;
    modCount++;
    return element;
}
總結:
LinkedList刪除操作是通過將index位置上的前一個節點的next執行index位置的后一個節點,同時將后一個節點的pred指向前一個節點,然后將index位置上的Node節點對象屬性都置空來實現的,置空后的Node對象會被GC垃圾回收期回收掉。
 
4、修改操作
public E set(int index, E element) {
    checkElementIndex(index);//檢查索引越界情況
    Node<E> x = node(index);//獲取index位置上的節點對象
    E oldVal = x.item;
    x.item = element;//將新插入的元素直接賦給此位置上節點對象的item屬性即可
    return oldVal;
}
總結:
LinkedList中的實際數據保存在節點對象的item屬性中
 
5、查詢操作
public E get(int index) {
    checkElementIndex(index);//檢查索引越界情況
    return node(index).item;//返回該index位置上的節點對象的item屬性,即該位置上的實際值
}
總結:
插敘操作是通過node方法實現的,而node方法是通過先判斷index位置是在整個鏈表的前一半還是后一半來遍歷前一半或者后一半元素來獲取index位置上的元素
 
6、LinkedList和ArrayList的大致區別:
1、ArrayList繼承於 AbstractList, LinkedList繼承於 AbstractSequentialList;
2、ArrayList基於數組, LinkedList基於雙向鏈表,對於隨機訪問, ArrayList比較占優勢,LinkedList插入、刪除元素比較快,如果只要調整指針的指向那么時間復雜度是O(1),但是如果針對特定位置需要遍歷時,時間復雜度是O(n),也就是LinkedList在隨機訪問元素的話比較慢;
3、LinkedList沒有實現自己的 Iterator,但是有 ListIterator和 DescendingIterator;
4、LinkedList需要更多的內存,因為 ArrayList的每個索引的位置是實際的數據,而 LinkedList中的每個節點中存儲的是實際的數據和前后節點的位置;
5、ArrayList 和 LinkedList都是非同步的集合。
6、和ArrayList一樣,LinkedList也是非線程安全的,只有在單線程下才可以使用。為了防止非同步訪問,可以采用如下方式創建LinkedList:List list= Collections.synchronizedList(new LinkedList());
7、LinkedList基於雙向鏈表實現,元素都可以為null。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM