Java 集合框架_LinkedList(源碼解析)

上一章進行了ArrayList源碼分析,這一章分析一下另一個重要的List集合LinkedList。

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

  public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{}

LinkedList與ArrayList對比發現:

  1. 它們繼承的基類不同,LinkedList繼承自AbstractSequentialList基類,AbstractSequentialList是AbstractList子類,這個類后面再說。
  2. LinkedList實現了Deque接口,代表它是一個隊列,準確地說它是一個雙端隊列。
  3. LinkedList沒有實現RandomAccess可隨機訪問標記接口,表示使用LinkedList的get(int index)獲取集合中元素的方法效率非常低。

一. Queue隊列接口

隊列是一種FIFO(先入先出)的數據結構,和它相對應的是一種叫做棧(LIFO后入先出)的數據結構。

1.1 棧

對于棧來說,我們想一想它應該有哪些方法?

  1. void push(E e); 向棧頂添加元素。
  2. E pop(); 移除棧頂元素,并返回它。
  3. E peek(); 查看棧頂元素。
  4. boolean isEmpty(); 棧是不是為空。
  5. int size(); 棧中元素的數量。
    要實現一個棧,實現這5個方法就可以了。

1.2 隊列

隊列與棧的方法應該差不多,只不過每次添加的時候,都是向隊列尾新添元素,而不是隊列頭。

  1. boolean offer(E e); 向隊列尾添加元素。
  2. E poll();移除隊列頭元素,并返回它。
  3. E peek(); 查看隊列頭元素。
  4. boolean isEmpty(); 隊列是不是為空。
  5. int size(); 隊列中元素的數量。
public interface Queue<E> extends Collection<E> {

    // 向隊列末尾新添加元素,返回true表示添加成功
   // 不會返回false,因為添加失敗直接拋出IllegalStateException異常。
   // 一般調用offer方法實現。
    boolean add(E e);

    // 向隊列末尾新添加元素,返回true表示添加成功,返回false,添加失敗
    boolean offer(E e);

    // 這個與Collection中的remove方法不一樣,因為Collection中的remove方法都要提供一個元素或者集合,用于刪除。
    // 這里不穿任何參數,就是代表刪除隊列第一個元素(即隊列頭),并返回它
    // 還需要注意的時,如果隊列是空的,即隊列頭是null,這個方法會拋出NoSuchElementException異常。
    E remove();

    // 這個方法也是刪除隊列第一個元素(即隊列頭),并返回它
    // 但是它和remove()方法不同的時,如果隊列是空的,即隊列頭是null,它不會拋出異常,而是會返回null。
    E poll();

    // 查看隊列頭的元素,如果隊列是空的,就拋出異常
    E element();

    // 查看隊列頭的元素。如果隊列是空的,不會拋出異常,而是返回null
    E peek();
}

可以看出繼承自Collection接口,那么size()和isEmpty()方法都由Collection接口提供,但是Queue接口還提供了是三個好像重復的方法。

  1. 向隊列尾添加元素的方法:add(E e)與offer(E e)。區別就是隊列是滿的,添加失敗時,add方法會拋出異常,而offer方法只會返回false。
  2. 移除隊列頭元素的方法:remove()與poll()。區別就是隊列為空的時候,remove方法會拋出異常,poll方法只會返回null。
  3. 查看隊列頭元素的方法:element()與peek()。區別就是隊列為空的時候,element方法會拋出異常,peek方法只會返回null。

下面是AbstractQueue中的實現

public abstract class AbstractQueue<E>
    extends AbstractCollection<E>
    implements Queue<E> {

    protected AbstractQueue() {
    }

    // 直接調用offer方法來實現,如果隊列是滿的,添加失敗,
    // 則拋出IllegalStateException異常
    public boolean add(E e) {
        if (offer(e))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }


    // 直接調用poll方法來實現,如果隊列是空的,移除元素失敗,
    // 則拋出NoSuchElementException異常
    public E remove() {
        E x = poll();
        if (x != null)
            return x;
        else
            throw new NoSuchElementException();
    }


    // 直接調用peek方法來實現,如果隊列是空的,查看元素失敗,
    // 則拋出NoSuchElementException異常
    public E element() {
        E x = peek();
        if (x != null)
            return x;
        else
            throw new NoSuchElementException();
    }
    ......
}

二. 雙端隊列

它與普通隊列相比較,它既可以在隊列頭添加元素,也可以在隊列尾添加元素;既可以在隊列頭刪除元素,也可以在隊列尾刪除元素。

注意一下,因為雙端隊列特性,所以它很容易實現一個棧,也就是說它本身可以當做棧使用。
根據雙端隊列的特性,它比普通隊列應該多了三個方法。

  1. boolean offerFirst(E e); 向隊列頭添加元素。
  2. boolean offerLast(E e); 向隊列尾添加元素。
  3. E pollFirst();移除隊列頭元素,并返回它。
  4. E pollLast();移除隊列尾元素,并返回它。
  5. E peekFirst(); 查看隊列頭元素。
  6. E peekLast(); 查看隊列尾元素。
  7. boolean isEmpty(); 隊列是不是為空。
  8. int size(); 隊列中元素的數量。
    public interface Deque<E> extends Queue<E> {

    // 向隊列頭添加元素
    void addFirst(E e);

    // 向隊列末尾新添加元素
    void addLast(E e);

    // 向隊列頭添加元素,和addFirst(E e)作用一樣,就是直接調用addFirst(E e)方法來實現。
    boolean offerFirst(E e);

    // 向隊列末尾新添加元素,和addLast(E e)作用一樣,就是直接調用addLast(E e)方法來實現。
    boolean offerLast(E e);

    // 刪除隊列第一個元素(即隊列頭),并返回它, 如果隊列是空的,這個方法會拋出NoSuchElementException異常。
    // 注,與Queue接口中remove()作用一樣,remove()方法就是調用removeFirst()方法來實現的
    E removeFirst();

    // 刪除隊列最后一個元素(即隊列尾),并返回它, 如果隊列是空的,這個方法會拋出NoSuchElementException異常。
    E removeLast();

    // 刪除隊列第一個元素(即隊列頭),并返回它, 如果隊列是空的,它不會拋出異常,而是會返回null。
    // 注,與Queue接口中poll()作用一樣,
    E pollFirst();

    // 刪除隊列最后一個元素(即隊列尾),并返回它, 如果隊列是空的,它不會拋出異常,而是會返回null。
    E pollLast();

    // 查看隊列頭的元素,如果隊列是空的,就拋出異常
    // 注,與Queue接口中element()作用一樣,
    E getFirst();

    // 查看隊列尾的元素,如果隊列是空的,就拋出異常
    E getLast();

    // 查看隊列頭的元素。如果隊列是空的,不會拋出異常,而是返回null
    E peekFirst();

    // 查看隊列尾的元素。如果隊列是空的,不會拋出異常,而是返回null
    E peekLast();

    // 從隊列頭都開始遍歷,找到與o相等的第一個元素刪除它,并返回true,如果沒找到就返回false,最多只刪除一個元素
    // 注,與Collection中remove(Object o)方法作用一樣
    boolean removeFirstOccurrence(Object o);
    // 從隊列尾都開始遍歷,找到與o相等的第一個元素刪除它,并返回true,如果沒找到就返回false,最多只刪除一個元素
    boolean removeLastOccurrence(Object o);

    // *** Queue methods ***

    boolean add(E e);

    boolean offer(E e);

    E remove();

    E poll();

    E element();

    E peek();


    // *** Stack methods ***

    // 向棧頂添加元素。與addFirst(E e)方法作用一樣
    void push(E e);

    // 移除棧頂元素,并返回它。如果棧為空的話,會拋出NoSuchElementException異常
    // 注,與removeFirst()方法一樣
    E pop();


    // *** Collection methods ***

    boolean remove(Object o);

    boolean contains(Object o);

    public int size();

    Iterator<E> iterator();

    Iterator<E> descendingIterator();

}

可以看出定義的接口中的方法比我們預計的多得多,主要是添加了一些隊列為空時,獲取元素會拋出異常的方法,還順便定義了棧的方法,因為雙端隊列很容易實現一個棧的功能。

雙端隊列Deque與普通隊列Queue相比較,就是多了從隊列頭插入,從隊列尾刪除,從隊列尾查看的功能。

三. AbstractSequentialList抽樣類

AbstractSequentialList這個類表示它的子類是使用鏈表這種數據結構來存儲集合元素的,而不是使用數組這種數據結構。這有什么不同呢?

  1. 數組的插入和刪除的效率都不高,因為可能涉及到數組元素的移動。但是訪問效率非常高,它支持隨機訪問,就是通過數組的下標直接獲取對應的元素。
  2. 鏈表的插入和刪除的效率都很高,因為只需要改變元素之間指向就可以了。但是訪問效率不高,它不支持隨機訪問,必須從鏈表頭或者鏈表尾開始一次訪問。

還記得我們在AbstractList方法中,怎么實現迭代器的么?

使用一個cursor屬性來記錄索引位置,然后通過調用List集合的get(int index)來獲取對應的元素。這里就不行了,因為通過get(int index)方法獲取集合元素的效率非常低。

而遍歷鏈表的方式就是獲取鏈表中一個元素,然后通過指向下一個元素的引用,不斷獲取下一個元素,直到為空,表示已經到了鏈表尾,而不是通過索引的方式。
所以我們思考一下AbstractSequentialList會做哪些事情。

  1. 將獲取迭代器的方法設置成abstract抽樣方法,強制子類提供迭代器方法,因為不能用索引這種低效率的方式獲取元素,所以強制子類去實現。
    // 調用listIterator方法,返回一個迭代器
    public Iterator<E> iterator() {
        return listIterator();
    }

    // 子類必須復寫這個方法,提供一個ListIterator迭代器。
    public abstract ListIterator<E> listIterator(int index);
  1. List集合可以通過索引得到集合中的元素,AbstractSequentialList集合也必須支持這種方式,雖然效率低。這時就可以通過ListIterator迭代器實現對應方法。

這里就與AbstractList中內部迭代器ListIterator類不同,AbstractList中迭代器是通過調用AbstractList中get(int index)和set(int index, E element)方法來實現對應功能的,所以AbstractList子類必須復寫這些方法。
而AbstractSequentialList是通過迭代器來實現本AbstractSequentialList對應方法,所以子類必須實現一個自定義迭代器。

  public abstract class AbstractSequentialList<E> extends AbstractList<E> {

    protected AbstractSequentialList() {
    }

    // 調用迭代器listIterator獲取
    public E get(int index) {
        try {
            // 迭代器會先根據index值,從鏈表頭開始遍歷,直到移動到index位置,將元素返回,所以效率不高。
            return listIterator(index).next();
        } catch (NoSuchElementException exc) {
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }

    // 調用迭代器listIterator的set設置
    public E set(int index, E element) {
        try {
            ListIterator<E> e = listIterator(index);
            E oldVal = e.next();
            e.set(element);
            return oldVal;
        } catch (NoSuchElementException exc) {
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }

    // 調用迭代器listIterator的add方法添加元素
    public void add(int index, E element) {
        try {
            listIterator(index).add(element);
        } catch (NoSuchElementException exc) {
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }
    // 調用迭代器listIterator的remove方法移除元素
    public E remove(int index) {
        try {
            ListIterator<E> e = listIterator(index);
            E outCast = e.next();
            e.remove();
            return outCast;
        } catch (NoSuchElementException exc) {
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }


    // Bulk Operations

    public boolean addAll(int index, Collection<? extends E> c) {
        try {
            boolean modified = false;
            ListIterator<E> e1 = listIterator(index);
            Iterator<? extends E> e2 = c.iterator();
            while (e2.hasNext()) {
                e1.add(e2.next());
                modified = true;
            }
            return modified;
        } catch (NoSuchElementException exc) {
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }


    // 會調用listIterator方法,返回一個迭代器
    public Iterator<E> iterator() {
        return listIterator();
    }

    // 子類必須復寫這個方法,提供一個ListIterator迭代器。
    public abstract ListIterator<E> listIterator(int index);
}

四. 單向鏈表和雙向鏈表

4.1 單向鏈表

簡單的說,元素除了包含本身的數據item,還有一個指向下一個元素的引用next。數據結構就像這樣:

 class Node<E> {
        E item;
        Node<E> next;

        Node( E element, Node<E> next) {
            this.item = element;
            this.next = next;
        }
    }

然后我們在看看單向鏈表插入和刪除。

  1. 插入:單向鏈表的插入,我們只需要改變兩個引用就可以了。
   private void insert(Node<E> prevNode, Node<E> node, Node<E> newNode) {
        // prevNode表示插入點前一個元素
        // node表示插入點元素
        // newNode表示要添加的元素,將它放入插入點(即前一個元素的next指向)
        // 并將newNode的next指向原來元素node
        if (prevNode != null) prevNode.next = newNode;
        newNode.next = node;
    }

在鏈表node元素前添加一個元素,就是將node元素前一個元素的next指向新元素newNode,再將新元素newNode的next指向node元素,這樣就把新元素newNode插入到鏈表中了。
注意要做一下前元素非空判斷,如果前元素為空表示插入點是鏈表頭。

根據我的經驗,先不考慮null的情況,改變對應引用,這里就是prevNode.next = newNode,newNode.next = node。然后我們再看看那些需要考慮null的情況。
比如這里prevNode就需要考慮null情況,否則會發生空指針異常。prevNode為空其實表示在鏈表頭。newNode是不允許為空。而node是不是為空對我們程序沒有任何影響。

  1. 刪除:單向鏈表的刪除,也只需要改變兩個引用就可以了。
    private void delete(Node<E> prevNode, Node<E> node) {
        // prevNode表示被刪除元素的前一個元素
        // node表示被刪除的元素
        if (prevNode != null) prevNode.next = node.next;
        node.next = null;
    }

刪除一個單向鏈表元素,就是將它的前一個元素的next指向它的next,這樣就在整個鏈表中查找不到這個元素了,然后將它的next設置為null。
當前一個元素為null,表示被刪除元素是鏈表頭,那么需要將表頭的next指向被刪除元素的next,這里沒有體現。

4.2 雙向鏈表

與單向鏈表相比,它的元素多了一個指向上一個元素的引用prev。數據結構就像這樣:

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

然后我們在看看雙向鏈表插入和刪除。

  1. 插入:雙向鏈表的插入,我們需要改變四個引用。
    private void insert(Node<E> node, Node<E> newNode) {
        // node表示插入點元素
        // newNode表示要添加的元素,將它插入到node元素之前

        // 將node前一個元素的next指向新元素newNode
        if(node.prev != null) node.prev.next = newNode;
        // 將新元素newNode的prev指向node前一個元素
        newNode.prev = node.prev;
        // 將node的prev指向新元素newNode,現在node的前一個元素變成新元素newNode
        node.prev = newNode;
        // 將新元素的next指向node,所以新元素的下一個元素是node
        newNode.next = node;
    }

要在元素node前插入一個新元素newNode。那么就需要四步:

  • 將node前一個元素的next指向新元素newNode
  • 將新元素newNode的prev指向node前一個元素
  • node元素的prev指向新元素
  • 新元素newNode的next指向node
  1. 刪除:雙向鏈表的刪除,也需要改變四個引用。
private void delete(Node<E> node) {
        // node表示要刪除的元素
        
        // 將node前一個元素的next指向node下一個元素
        if (node.prev != null) node.prev.next = node.next;
        // 將node下一個元素的pre指向node前一個元素
        if (node.next != null) node.next.prev = node.prev;
        
        // 將node的prev和next都置位null
        node.prev = null;
        node.next = null;
    }

注意只考慮本節點元素情況,沒有考慮鏈表頭的賦值。

五. LinkedList 類

5.1 成員屬性

    // 集合數量
    transient int size = 0;

    // 雙向鏈表的表頭
    transient Node<E> first;
 
    // 雙向鏈表的表尾
    transient Node<E> last;

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

通過一個雙向鏈表來記錄集合中的元素。

5.2 構造函數

    public LinkedList() {
    }

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

LinkedList的構造函數比較簡單,因為它不用想ArrayList那樣,要確定初始數組的長度。

5.3 添加元素

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

向鏈表尾添加一個新元素newNode,要進行以下幾個步驟:

  1. 將鏈表尾last賦值給變量l,因為表尾last要指向新元素newNode
  2. 創建新元素newNode,根據Node的構造函數,我們知道新元素newNode的prev指向l(即表尾),next還是為null。
  3. 將表尾last指向新元素newNode
  4. 將原表尾l的next指向新元素,這時要考慮一種情況,原表尾l為null,即整個鏈表是空的,那么這個時候,我們只需要將表頭first也指向新元素newNode就可以了。
  5. 集合數量size加1,以及modCount自增表示集合已經修改了。

注意,這里好像只改變了三個應用,缺少了新元素newNode下一個元素的prev指向新元素newNode。這是因為在表尾,不存在下一個元素。

  public void add(int index, E element) {
        checkPositionIndex(index);
        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }

在鏈表指定索引位置插入元素,如果index等于size,表示在表尾插入元素,直接調用linkLast(element)方法,否則先調用node(index)方法,找到index索引對應元素node,并將要添加元素element插入到元素node之前。

   Node<E> node(int index) {
        // 如果index小于集合數量的一半,那么從表頭開始遍歷,一直到index位置。
        // 否則從表尾開始遍歷,一直到index位置。這樣我們每次最多遍歷size/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;
        }
    }

這里用了個巧妙方法,先判斷index在集合的前一半還是后一半,決定從鏈表頭還是鏈表尾遍歷。

   void linkBefore(E e, Node<E> succ) {
        // e表示新添加的元素
        // succ表示被插入的元素(即新元素插入到這個元素之前)
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

在succ元素之前插入新元素e,要進行以下幾個步驟:

  1. 將元素succ的前一個元素賦值給變量pred
  2. 創建新元素newNode。 新元素newNode的prev指向pred,next指向succ。
  3. 將元素succ的prev指向新元素newNode。
  4. 將元素pred的next指向新元素newNode。但是考慮一種情況,pred為null,即元素succ就是鏈表頭,那么新添加元素就變成新表頭了,first = newNode。
  5. 集合數量size加1,以及modCount自增表示集合已經修改了。
public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }

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

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

        // pred表示index位置前一個元素,succ表示index位置元素
        Node<E> pred, succ;
        if (index == size) {
            // 當index == size時,index位置的元素為null,它的前一個元素是表尾last元素
            succ = null;
            pred = last;
        } else {
            // 通過ode(index)方法,查找index位置元素
            succ = node(index);
            pred = succ.prev;
        }

        // 遍歷要插入集合c的元素,將它們插入到本集合中
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            // 將新元素的prev指向前一個元素pred
            Node<E> newNode = new Node<>(pred, e, null);
            // pred為空表示,插入點在表頭,所以將新元素設置為表頭
            if (pred == null)
                first = newNode;
            else
                // 將前一個元素pred的next指向新元素newNode
                pred.next = newNode;
            // pred指向新元素,然后繼續遍歷
            pred = newNode;
        }

        // pred現在表示插入集合元素最后一個元素

        // succ為空表示在表尾插入集合,那么插入集合中最后一個元素就成為新的表尾
        if (succ == null) {
            last = pred;
        } else {
            // 將插入集合中最后一個元素和插入點index位置元素進行聯系。
            pred.next = succ;
            succ.prev = pred;
        }

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

在集合index位置前插入一個集合c中所有元素。可以這么做:

  1. 先找到index位置元素succ,和它前一個元素pred。
  2. 遍歷集合c中元素,將它們插入到元素pred之后,即新元素newNode.prev = pred, pred.next = newNode。然后將 pred = newNode; 再依次遍歷集合c。
  3. 遍歷完成之后,pred就指向集合c最后一個添加的元素。這時就要讓它和index位置元素發生聯系。

當然這個過程中還要考慮表頭和表尾的改變。

5.4 刪除元素

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

遍歷整個集合,找到與o相同元素,調用unlink方法刪除這個元素,如果沒有找打,就返回false。

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

這個方法和我們前面寫的雙向鏈表刪除方法一樣。主要就是

  1. 被刪除元素x的前一個元素的next指向被刪除元素后一個元素。
  2. 被刪除元素x后一個元素的prev指向被刪除元素x前一個元素。
  3. 最后將刪除元素x的prev與next都設置為null。
  4. 當然要注意下表頭和表尾的判斷,如果被刪除元素x的prev為null,表示x是表頭,那么就要將表頭first指向元素x的下一個元素。如果被刪除元素x的next為null,表示x是表尾,那么就要將表尾last指向元素x前一個元素。
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

通過node(index)方法,獲取index索引對應元素,然后調用unlink(Node<E> x) 方法刪除這個索引。

   public void clear() {
        // 遍歷鏈表,將鏈表中的引用都置位null,方便垃圾回收器釋放內存
        for (Node<E> x = first; x != null; ) {
            Node<E> next = x.next;
            x.item = null;
            x.next = null;
            x.prev = null;
            x = next;
        }
        first = last = null;
        size = 0;
        modCount++;
    }

將鏈表中元素的引用都置位null,方便垃圾回收器回收。

注 boolean removeAll(Collection<?> c)與boolean retainAll(Collection<?> c)都是使用AbstractCollection抽樣類的默認實現。也就是通過迭代器Iterator來刪除集合中元素。

5.5 替換元素

   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)查找出元素,將元素中數據賦值給oldVal,再將新數據element設置到元素中,最后返回老數據oldVal。

5.5 查找元素

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

從表頭開始遍歷,查找第一個與o值相等元素,返回對應索引,如果沒找到就返回-1 。

    public int lastIndexOf(Object o) {
        int index = size;
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (x.item == null)
                    return index;
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (o.equals(x.item))
                    return index;
            }
        }
        return -1;
    }

從表尾開始遍歷,查找第一個與o值相等元素,返回對應索引,如果沒找到就返回-1 。

    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

通過node(index)方法找到對應索引的元素,然后返回元素的值。

    public ListIterator<E> listIterator(int index) {
        checkPositionIndex(index);
        return new ListItr(index);
    }

返回LinkedList內部的一個迭代器。這個類我們之后會詳細介紹。

5.6 其他重要方法

   public Object[] toArray() {
        Object[] result = new Object[size];
        int i = 0;
        for (Node<E> x = first; x != null; x = x.next)
            result[i++] = x.item;
        return result;
    }

將集合轉成一個Object[]數組,先創建一個長度為集合數量size的Object[]數組,然后遍歷鏈表,將元素中數據item存放到數組中。

   public <T> T[] toArray(T[] a) {
        if (a.length < size)
            a = (T[])java.lang.reflect.Array.newInstance(
                                a.getClass().getComponentType(), size);
        int i = 0;
        Object[] result = a;
        for (Node<E> x = first; x != null; x = x.next)
            result[i++] = x.item;

        if (a.length > size)
            a[size] = null;

        return a;
    }

將集合轉成T類型的數組。如果數組a的長度小于集合數量size,那么就要創建一個新數組,再賦值給a,然后遍歷鏈表,將元素中數據item存放到數組a中。

這里有個很詭異的地方,就是if (a.length > size)這個判斷。我們知道數組a中 0 -- size-1 位置的元素都是集合中的,那么從size位置開始之后的元素都是數組a原有的元素,這里不知道為什么單單將size位置元素置位null。

六. LinkedList內部類ListItr

6.1 成員屬性

        // 代表當前遍歷到的元素
        private Node<E> lastReturned;
        // 表示迭代器開始的元素
        private Node<E> next;
        // 表示元素next在鏈表中的位置,與next是相對應的。
        private int nextIndex;
        private int expectedModCount = modCount;

6.2 構造函數

        ListItr(int index) {
            // 先通過LinkedList的node方法,查找index索引位置對于的元素,賦值給next
            next = (index == size) ? null : node(index);
            // 將index賦值給 nextIndex
            nextIndex = index;
        }

6.3 正向遍歷集合

        public boolean hasNext() {
            return nextIndex < size;
        }

當nextIndex小于集合數量size,說明集合還有元素沒有遍歷到。

      public E next() {
            checkForComodification();
            if (!hasNext())
                throw new NoSuchElementException();

            lastReturned = next;
            next = next.next;
            nextIndex++;
            return lastReturned.item;
        }

將next賦值給lastReturned,再將next指向它的下一個元素,然后將nextIndex自增,最后返回當前元素lastReturned的數據item。

6.4 反向遍歷

       public boolean hasPrevious() {
            return nextIndex > 0;
        }

        public E previous() {
            checkForComodification();
            if (!hasPrevious())
                throw new NoSuchElementException();

            lastReturned = next = (next == null) ? last : next.prev;
            nextIndex--;
            return lastReturned.item;
        }

這里做了一個處理,還記得在ListItr構造函數中,如果index == size,那么next就賦值為null,所以這里當next == null就從表尾開始向前遍歷。

6.5 返回索引

       public int nextIndex() {
            return nextIndex;
        }

        public int previousIndex() {
            return nextIndex - 1;
        }

這個已經在AbstractList中的詳細介紹過了。

6.6 操作集合的方法

      public void remove() {
            checkForComodification();
            if (lastReturned == null)
                throw new IllegalStateException();

            // 將當前元素下一個元素賦值給lastNext
            Node<E> lastNext = lastReturned.next;
            // 調用LinkedList集合的unlink方法,刪除當前元素
            unlink(lastReturned);
            // 如果next == lastReturned,表示反向遍歷。
            // 將next指向lastNext,因為lastNext的前一個就是原lastReturned前一個元素,所以不會有遺漏
            if (next == lastReturned)
                next = lastNext;
            else
                // 表示正向遍歷,那么刪除當前元素,只有一個影響,就是集合數量減少了。
                // 而正向遍歷結束條件時nextIndex < size,所以要將nextIndex自減。
                // 而反向遍歷是結束條件是nextIndex > 0,所以不需要處理
                nextIndex--;
            lastReturned = null;
            expectedModCount++;
        }

        public void set(E e) {
            if (lastReturned == null)
                throw new IllegalStateException();
            checkForComodification();
            // 超級簡單,就是將當前元素的數據item設置成e
            lastReturned.item = e;
        }

        public void add(E e) {
            checkForComodification();
            lastReturned = null;
            // 如果next == null,就在鏈表尾插入元素e
            if (next == null)
                linkLast(e);
            else
                // 不然就在next元素之前插入元素e
                linkBefore(e, next);
            nextIndex++;
            expectedModCount++;
        }

總結

LinkedList不僅是一個List集合,它還是一個隊列,或者說是雙端隊列。

棧是一個后入先出(LIFO)的數據結構,主要是三個方法:

  1. void push(E e); 向棧頂添加元素。
  2. E pop(); 移除棧頂元素,并返回它。
  3. E peek(); 查看棧頂元素。

隊列

隊列是一個先入先出(FIFO)的數據結構,主要是三個方法:

  1. boolean offer(E e); 向隊列尾添加元素。
  2. E poll();移除隊列頭元素,并返回它。
  3. E peek(); 查看隊列頭元素。

雙端隊列

雙端隊列與普通隊列做比較,它既可以在隊列頭添加元素,也可以在隊列尾添加元素;既可以在隊列頭刪除元素,也可以在隊列尾刪除元素。
它的主要方法有六個:

  1. boolean offerFirst(E e); 向隊列頭添加元素。
  2. boolean offerLast(E e); 向隊列尾添加元素。
  3. E pollFirst();移除隊列頭元素,并返回它。
  4. E pollLast();移除隊列尾元素,并返回它。
  5. E peekFirst(); 查看隊列頭元素。
  6. E peekLast(); 查看隊列尾元素。

AbstractSequentialList抽樣類

AbstractSequentialList這個類表示它的子類是使用鏈表這種數據結構來存儲集合元素的,而不是使用數組這種數據結構。也就是說它沒有可隨機訪問能力。

單向鏈表和雙向鏈表

注意一下單向鏈表和雙向鏈表的插入和刪除。
單向鏈表的插入和刪除最多改變兩個引用,而雙向鏈表的插入和刪除最多改變四個引用。

LinkedList 類

使用first表示雙向鏈表的表頭,使用last表示雙向鏈表的表尾。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,253評論 2 375

推薦閱讀更多精彩內容