Java集合之LinkedList源碼分析

LinkedList

  1. 特點
    1. 允許null值
    2. 內部以雙向鏈表的形式來保存集合中的元素查詢慢,增刪快(相比於ArrayList少了數組拷貝)
    3. 線程不安全
    4. 所有指定位置的操作都是從頭開始遍歷進行的
    5. 素是有序的,輸出順序與輸入順序一致

2.理論與實操同樣重要,知其然也要知其所以然

LinkedList查詢慢,增刪快與ArrayList相反,但同時都是線程不安全的

首先分析其構造方法

//空參構造(使用最多)
public LinkedList() {
    }
//有參構造
public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);//添加所有集合元素
    }

分析方法實現原理源碼

1. 添加方法其一

  • 原理:
    首先看單向鏈表添加如何實現:
    點擊這裏可以自行操作觀察
    在這裏插入圖片描述
    每次增加一個元素,最後一個元素節點會指向新增加元素(2指向3),我習慣稱爲前一個元素的後繼集節點指向這個元素
    同理,在java中,因爲是雙向鏈表所以,還有一個節點,我習慣稱爲元素的前驅節點指向前一個元素(3指向2)
    下面探究java中如何實現這一操作
/
 public boolean add(E e) {
        linkLast(e);//實則調用這個方法,如下
        return true;
    }
/**
 * 先分析: 1、雙向鏈表包括首節點first,尾節點last,元素節點
 *        2、每一個元素節點包括三部分:元素、前驅節點、後繼節點
 *        3、添加元素又分爲首次添加、非首次添加
 *        4、首次添加:first爲空節點,last爲空節點,此時創建新元素節點
 *      前繼節點則爲空,後繼節點也爲空,添加元素後,此元素節點既是first也爲last
 *        5、非首次添加:創建新元素節點,添加到鏈表最後,此時應讓新元素節點的前驅節點指向當前尾節點(在創建新節點時調用的構造函數中完成)
 *      然後新元素變爲新的尾節點,因爲是雙向鏈表,需要之前尾節點的後繼節點同樣指向新的尾節點節點完成雙向
 */
 void linkLast(E e) {
        final Node<E> l = last;//此時l代表當前尾節點
        /**創建一個節點:l代表尾節點(元素前驅節點)、e當前節點、null代表尾節點(元素後繼節點)*/
        final Node<E> newNode = new Node<>(l, e, null);//Node方法如下文
        //此時讓尾節點指向新添加的元素,作爲尾節點的標記(上述新的尾節點)
        last = newNode;
        if (l == null)//在第一次添加元素時,last爲空所以l爲空,則這個元素就是第一個節點(首節點)
            first = newNode;//引出首節點,作爲首節點的標記(同時也是尾節點)
        else//若不是第一次添加元素,則尾節點last不爲空,l則也不爲空,
            l.next = newNode;//(爲了滿足雙向鏈表)當前尾節點的後繼節點指向新添加的元素節點,至此新添加元素節點變爲尾節點(完成雙向鏈接)
        size++;//鏈表元素個數加一
        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;//在創建新節點時,前繼節點在這裏完成了指向當前尾節點
        }
    }

2. 添加方法其二

原理:時刻考慮雙向鏈表特性,指定位置添加元素時,改變新增元素前後繼節點,以及後元素節點的前指向,和前元素節點的後指向,下圖上方爲新增節點,在1處新增示意圖(大概一個示意圖,有點醜),與上述唯一區別就是此方法時在中間添加元素
在這裏插入圖片描述
源碼:

//index指定添加元素的位置,element元素值
 public void add(int index, E element) {
 		//判斷索引是否越界或者非法(小於零或者大於鏈表長度)
        checkPositionIndex(index);
		//若index==size證明需要在鏈表末尾添加元素(和第一種添加方法實現一致)
        if (index == size)
            linkLast(element);//同上方法一
        else//反之,在指定索引添加元素 首先node(index)找出該索引對應的元素節點
            linkBefore(element, node(index));
    }

void linkBefore(E e, Node<E> succ) {
        // 獲取指定位置元素的前驅節點
        final Node<E> pred = succ.prev;
        //創建新節點,前驅節點指向原指定位置的前繼節點,後繼結點指向succ節點
        //例:1<——2(newNode)——>3
        final Node<E> newNode = new Node<>(pred, e, succ);
        //上述例子 因爲是雙向鏈表所以要同時讓2<——3
        succ.prev = newNode;
        if (pred == null)
        //pred 爲空證明新添加的元素是第一個節點,那麼更新首節點
            first = newNode;
        else
        //反之,也要設置1——>2,保證雙向鏈表
            pred.next = newNode;
        //元素個數加一
        size++;
        //記錄操作次數
        modCount++;
    }

其他的添加方法原理基本一樣

3. 刪除方法

  • 原理就是完成下圖的整個過程(有了想法纔有可能實現!)

在這裏插入圖片描述指定對象刪除

public boolean remove(Object o) {
        if (o == null) {//若刪除null元素
            for (Node<E> x = first; x != null; x = x.next) {//從第一個元素開始,首節點給x,判斷條件是x是否有值,有的話進入循環體,然後將x的後繼節點付給x(後繼節點就相當於下一個元素)
                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;
    }

指定索引刪除

public E remove(int index) {
        checkElementIndex(index);//判斷索引是否爲合法(小於零或者大於鏈表長度)
        return unlink(node(index));//node(index)找到元素節點直接刪除如下
    }


 Node<E> node(int index) {
        //根據索引查找元素這裏體現了雙向鏈表
        if (index < (size >> 1)) {//index小於元素總個數的一半,從前找更快
        	//從前往後找
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;//順藤摸瓜找到index位置元素
            return x;
        } else {//反之從後找更快
        //從後往前找
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

發現上述方法就是通過傳入的條件找到了要刪除的元素最終通過unlink()方法刪除下面分析這個方法

 E unlink(Node<E> x) {
 		//x就是要刪除的節點,從節點中取出要刪除的元素
        final E element = x.item;
        //取出後繼節點
        final Node<E> next = x.next;
        //取出前驅節點
        final Node<E> prev = x.prev;
		//下方兩個if語句的執行代表了上圖過度過程的實現(解鏈操作)
        if (prev == null) {//若前驅節點爲null證明,這是第一個元素
            first = next;//將要刪除的元素節點的下一個元素節點作爲第一個元素節點(後繼節點指向的元素)
        } else {//反之則爲中間元素節點
            prev.next = next;//上圖中過度過程的上方藍線形成1的後繼節點指向3(通過2),同時1指向2紅線斷裂(被重新賦值覆蓋了)
            x.prev = null;//斷開2的前驅節點(斷開了2指向1的那條紅線)
        }

        if (next == null) {//若是最後一個元素
            last = prev;//將最後一個元素的前一個元素設置爲最後一個元素,記錄到尾節點中
        } else {
            next.prev = prev;//上圖中過度過程的下方藍線形成3的前驅節點指向1(通過2)同時3指向2紅線斷裂(被重新賦值覆蓋了)
            x.next = null;//斷開2的後繼節點(斷開了2指向3的那條紅線)
        }
		//最後清空元素,到此元素節點被成功刪除
        x.item = null;
        size--;//元素個數自減
        modCount++;//操作次數自增
        return element;//返回這個被刪除的元素
    }

掌握上述方法後再看其他相關刪除方法代碼,邏輯都一樣

4. 查找元素get()方法

 public E get(int index) {
        checkElementIndex(index);
        return node(index).item;//在刪除方法中已經說過node方法,得到節點後取出元素返回
    }

5. 遍歷

  • 原理:從第一個元素或者最後一個元素開始遍歷,邏輯就是根據得到的第一個元素節點順藤摸瓜到最後一個元素節點,比較簡單不累贅了,(可參考上篇文章arrayList的遍歷原理一樣,只不過鏈表是以元素向下尋找,有興趣可自行跟進源碼查看),遍歷耗時很慢,每次查找都要從頭開始,一般不採用。

好了LinkedList討論到此結束,下篇文章討論List集合下的Vector,每天進步一點點!!大家一起討論學習,不足的地方還請指出來!

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