JDK集合源碼解析——LinkedList

前言

上一篇文章對ArrayList的源碼進行了一個簡單的解析,那麼本篇將對ArrayList的“兄弟”集合類LinkedList進行解析,相對於ArrayList而言,LinkedList的源碼相對複雜一點點,但還沒難道難以閱讀的地步,稍微用點心還是可以滴。

那麼說到LinkedList,在面試的時候都會問他的數據結構是什麼,時間複雜度是什麼等等。有時候一些問題是成鏈式結構的,譬如LinkedList的時間複雜度是由其數據結構決定的,當你瞭解了它的數據結構,後面相應的問題也都可以解決了。

 

一、LinkedList的基本實現

在解析之前,先來看下LinkedList類的UML類圖:

上圖很清晰的展示了LinkedList的類的繼承結構,那我們來做個簡單的分析。

實現:

        1)List接口:具有list接口中的相關功能

        2)Deque:繼承了Deque接口,說明了LinkedList具有雙向隊列的功能

                3)Cloneable:說明LinkedList是具有克隆的功能

                4)Serializable:說明其可被序列化

        繼承:

               AbstractSequentialList:LinkedList=> AbstractSequentialList=>AbstractList=> AbstractCollection—> Collection => Iterable

 

二、LinkedList的基本信息

下面代碼中是LinkedList之後基本屬性和內部類:

    // LinkedList大小
    transient int size = 0;

    // 頭節點
    transient Node<E> first;

    // 尾結點
    transient Node<E> last;

    // 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內的構造方法:

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

    // 有參構造,傳入一個集合作爲參數來初始化
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

    // 通過調用addAll(size,c) 完成集合的添加
    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }

    // 
    public boolean addAll(int index, Collection<? extends E> c) {
        // 檢查index是否符合規定
        checkPositionIndex(index);

        // 獲取數據,並轉換成數組
        Object[] a = c.toArray();
        // 獲取數組長度
        int numNew = a.length;
        // 如果需要添加的數組長度爲0,則直接返回
        if (numNew == 0)
            return false;

        // 定義兩個節點,pred爲待插入節點的前一個節點,succ爲待添加節點的位置
        Node<E> pred, succ;
        // 如果index == size,則把succ置爲null,pred指向尾結點
        // 反之,則把succ插入待插入的位置
        if (index == size) {
            succ = null;
            pred = last;
        } else {
            succ = node(index);
            pred = succ.prev;
        }

        // 遍歷數組,沒遍歷一次新建一個節點,然後存入a中的數據
        // 如果帶插入節點的前一個節點爲空,則把待插入的節點設爲首節點
        // 否則插入當前節點的後面,然後再指向前一個節點,形成雙向鏈表
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            Node<E> newNode = new Node<>(pred, e, null);
            if (pred == null)
                first = newNode;
            else
                pred.next = newNode;
            pred = newNode;
        }


        // 如果succ==null,把添加的新節點指向最後一個位置,last指向pred
        // 當不爲空的時候,則把pred的next指向succ,succ的prev指向pred
        // 然後把集合的大小設置爲新的大小
        // modCount 自增,爲修改的次數
        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }

三、添加方法

    // 添加元素
    public boolean add(E e) {
        linkLast(e);
        return true;
    }


    // 新添加元素在尾結點
    void linkLast(E e) {
        // 把last指向l
        final Node<E> l = last;
        // 新建節點,用於存儲元素,然後設置自身爲尾結點
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        // 如果l爲null,則則說明原來數據就爲空,那麼就把新節點設爲頭節點,反之則把l的next指向新節點
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

    // 添加到指定位置
    public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            // 如果index等於size,則繼續從尾部添加
            linkLast(element);
        else
            // 在非空節點前插入數據
            linkBefore(element, node(index));
    }


    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        // 新建一個變量,把succ指向新建節點的前一個節點
        final Node<E> pred = succ.prev;
        // 新建節點,把它的前直接點prev設置爲剛纔新建的變量,後置節點設置爲succ
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        // 如果pred爲null,則把新節點設爲頭節點,反之,則把succ的前一個節點的next設爲新節點
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

四、刪除方法

    // 根據傳入對象進行刪除
    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;
    }

    // 根據index來刪除數據
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

    // 刪除首節點數據
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

    // 刪除尾結點數據
    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }

具體調用方法實現:

    // 
    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;

        // 如果爲null,則說明是頭節點,把next置爲first
        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        // 如果爲null,則說明是最後一個節點,把prev置爲last
        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

    // 刪除第一個元素
    private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        // 先定義一個變量,用來指向待刪除的節點的值,再定義一個變量指向待刪除的節點的下一個節點
        final E element = f.item;
        final Node<E> next = f.next;
        // 把f的值和next置爲null
        f.item = null;
        f.next = null; // help GC
        first = next;
        // 判斷它的下一個節點是否爲null,如果爲null,則需要把last設置爲null
        // 否則需要把next的prev設置爲null,因爲next現在指代頭節點
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

    // 所實現的思路和上面的方法差不多
    private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        final E element = l.item;
        final Node<E> prev = l.prev;
        l.item = null;
        l.prev = null; // help GC
        last = prev;
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        modCount++;
        return element;
    }

五、修改方法

    // 首先檢查索引是否合法,然後通過index獲取舊值,然後把新值替換即可,最後把舊值返回去
    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

六、修改方法

   // 1、按照 index來刪除元素,這裏並沒有從開頭曲遍歷刪除,而是先判斷index更靠近頭節點還是尾結點
   // 然後再進行遍歷刪除
   Node<E> node(int index) {
        // assert isElementIndex(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;
        }
    }

    // 2、根據傳入對象刪除數據,主要調用indexOf(o)方法來判斷元素的位置
    public boolean contains(Object o) {
        return indexOf(o) != -1;
    }

    // 首先判斷對象是否爲空,然後遍歷node,在判斷是否包含對象中的元素,
    // 如果有,則返回對應的位置index,如果找不到,則返回-1
    public int indexOf(Object o) {
        int index = 0;
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null)
                    return index;
                index++;
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        return -1;
    }

 

總結:

  1. LinkedList底層是一個雙向鏈表,且是一個直線型的鏈表結構。 
  2. LinkedList 進行節點插入、刪除卻要高效得多,但是隨機訪問性能則要比動態數組慢
  3. LinkedHashSet,內部構建了一個記錄插入順序的雙向鏈表,因此提供了按照插入順序遍歷的能力,與此同時,也保證了常數時間的添加、刪除、包含等操作,這些操作性能略低於 HashSet,因爲需要維護鏈表的開銷。
  4. 最後建議童鞋們 能自己動手寫一個簡單的LinkedList

 

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