「必知必會」最細緻的 LinkedList 原理分析

梓川耶發表於2021-08-09

1.結構

 

1. 繼承

 

  該類繼承自 AbstractSequentialList 這個是由於他是一個順序的列表,所以說繼承的是一個順序的 List

 

2. 實現

 

這個類實現的介面比較多,具體如下:

 

  1. 首先這個類是一個 List 自然有 List 介面
  2. 然後由於這個類是實現了 Deque 這個介面是雙端佇列的介面,所以說它是具有雙端佇列的特性的。後面我們會看到很多關於雙端佇列的方法。
  1. 然後就是兩個集合框架肯定會實現的兩個介面 Cloneable, Serializable 。

3. 主要欄位

 

1. 屬性欄位

 

    transient int size = 0;
    //指向連結串列的頭指標和尾指標
    transient Node<E> first;
    transient Node<E> last;

 

2. Node 節點

 

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. 主要方法概覽

 

  1. ctor-2
  2. addFirst
  1. addLast
  2. addAll
  1. add
  2. indexOf
  1. lastIndexOf
  2. peek 獲取第一個元素,是 null 就返回 null
  1. peekFirst/Last  獲取第一個最後一個元素
  2. poll 刪除第一個元素並返回 沒有返回 null
  1. pollFirst/Last
  2. offer 呼叫了 add
  1. offerFirst/Last
  2. push
  1. pop
  2. set
  1. remove(noArgs) == removeFirst  繼承自 deque
  2. remove(E e) 查詢刪除
  1. read/writeObject  還是手動的序列化,原因一樣,直接序列化元素而沒有 pre/next

 

2. 構造方法分析

 

只有兩個構造方法。其中一個是預設的空構造也就是生成一個空的 LinkedList 另外一個就是接受一個 Collection 介面。裡面呼叫了 PutAll 方法。

 

    public LinkedList() {
    }

    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

 

3. 主要方法分析

 

1. add

 

這個方法就直接呼叫了 linkLastlinkLast 裡面就是直接把元素新增到元素的結尾。

 

 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);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
}

 

2. addFrist/Last

 

這兩個方法同上還是呼叫了 linkFirstlinkLast 所以說這幾個新增修改的方法基本都是靠底層的同樣的方法實現的。

 

public void addFirst(E e) {
   linkFirst(e);
}

public void addLast(E e) {
   linkLast(e);
}

 

3. addAll

 

該方法我們在構造方法中也看到了,在它裡面實現的時候和 ArrayList 一樣是直接把集合轉成陣列,然後進行建立新的節點插入進來。

 

public boolean addAll(int index, Collection<? extends E> c) {
       checkPositionIndex(index);

       Object[] a = c.toArray();
       int numNew = a.length;
       if (numNew == 0)
           return false;

       Node<E> pred, succ;
       if (index == size) {
           succ = null;
           pred = last;
       } else {
           succ = node(index);
           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;
           else
               pred.next = newNode;
           pred = newNode;
       }

       if (succ == null) {
           last = pred;
       } else {
           pred.next = succ;
           succ.prev = pred;
       }

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

 

4. indexOf

 

這個方法裡面採用 for 迴圈遍歷,遍歷的時候是從頭結點開始遍歷,只要找到那個元素立即返回,而不繼續進行下去。

 

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

 

5. lastIndexOf

 

這個方法和上面的方法實現的方式一樣的,但是注意這個方法的意思是找到最後一個與之匹配的元素,他並不是從頭開始找,而是直接從尾節點開始遍歷。做法同理找到即停止。

 

6. peek/peekFirst/peekLast

 

peek 方法的意思就是返回最頂端的元素,如果這個元素不存在,那麼直接返回 null 。之後還有 peekFirst 這類的就是返回第一個的意思。底層呼叫的就是頭結點的屬性。這些方法其實在 Collection 介面中是不存在的,主要就是因為他實現了 Deque 所帶來的的新特性。

 

7. poll/pollFirst/pollLast

 

poll 用來刪除頭結點並返回,如果不存在就返回 null
剩下的兩個方法同理。

 

8. offer/offerFirst/offerLast

 

插入頭結點。

 

9. push/pop

 

底層的方法就是 addFirst 和 removeFirst

 

10. remove(noargs)/remove(E e)

 

無參的呼叫 removeFirst 有引數的就是去查詢然後刪除。

 

11. read/writeObject

 

這裡同 ArrayList 自己手動的進行了序列化。序列化的時候只是對 Node 節點裡面的元素進行序列化,而前驅後繼直接省略,也是節約空間的想法。

 

4.總結

 

好,其實在完全理解 ArrayList 的基礎之上看這篇文章就比較好理解,裡面的操作更加簡單。只是注意一下兩者的區別,實現了 Deque 帶來的不少新的方法。

相關文章