集合類源碼解析——LinkedList

類圖

成員變量

  • sizejava

    LinkedList 中容納的元素個數node

  • first數組

    LinkedList 的頭結點數據結構

  • last優化

    LinkedList 的尾結點this

內部類

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;
        }
    }
複製代碼

Node 類用來實際存儲 LinkedList 元素以及維護各元素之間的關係。spa

構造方法

public LinkedList()

構造一個空 listcode

public LinkedList(Collection<? extends E> c)

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

構造空 list,以後將 Collection 類型集合中的元素添加進去。cdn

重要方法

add(E e)

public boolean add(E e) {
        linkLast(e);
        return true;
    }
複製代碼

linkLast(E e)

void linkLast(E e) {
        final Node<E> l = last;
        // 構造新節點做爲尾結點,將原來的尾結點 l 做爲後繼節點
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        // 若是原來的尾結點 l 爲空,說明該 LinkedList 只有該新節點,所以也將新節點標記爲頭結點
        if (l == null) {
            first = newNode;
        } else {
            l.next = newNode;
        }
        size++;
        modCount++;
    }
複製代碼

add 方法與 addLast 方法實現一致,區別在於 add 方法會返回布爾值,而 addLast 沒有返回值。blog

addFirst(E e)

public void addFirst(E e) {
        linkFirst(e);
    }
複製代碼

linkFirst(E e)

private void linkFirst(E e) {
        final Node<E> f = first;
        // 構造新節點做爲頭結點,將原來的頭結點 f 做爲後繼節點
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        // 若是原來的頭結點 f 爲空,說明該 LinkedList 只有該新節點,所以也將新節點標記爲尾結點
        if (f == null) {
            last = newNode;
        } else {
            f.prev = newNode;
        }
        size++;
        modCount++;
    }
複製代碼

add(int index, E element)

public void add(int index, E element) {
        checkPositionIndex(index);//檢查索引範圍
        // 若是 index == size,說明插入的位置是最後一位,直接調用 linkLast 方法
        if (index == size) {
            linkLast(element);
        } else {
            linkBefore(element, node(index));
        }
    }
複製代碼

linkBefore(E e, Node<E> succ)

void linkBefore(E e, Node<E> succ) {
        // assert succ != null;(方法訪問級別爲 default,因此不作空判斷)
        final Node<E> pred = succ.prev;
        // 構造新節點爲 succ 的前置結點,pred 的後置結點
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        // 若是 pred 爲 null,說明 succ 爲原來的頭結點。將新節點做爲頭結點
        if (pred == null) {
            first = newNode;
        } else {
            pred.next = newNode;
        }
        size++;
        modCount++;
    }
複製代碼

addAll(Collection<? extends E> c)

public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }
複製代碼

addAll(int index, Collection<? extends E> 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;
        }

        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;
        }
        // 添加完後,維護添加的最後一個結點與 succ 結點的關係
        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }
        // 維護 LinkedList 的元素個數
        size += numNew;
        modCount++;
        return true;
    }
複製代碼

remove()

public E remove() {
        return removeFirst();
    }
複製代碼

removeFirst()

public E removeFirst() {
        final Node<E> f = first;
        if (f == null) {
            throw new NoSuchElementException();
        }
        return unlinkFirst(f);
    }
複製代碼

unlinkFirst(Node<E> f)

private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC
        first = next;
        // 若 f 的後置結點爲 null,說明 LinkedList 中已沒有元素,更新 last 爲 null
        if (next == null) {
            last = null;
        } else {
            next.prev = null;
        }
        size--;
        modCount++;
        return element;
    }
複製代碼

unlinkLast 方法與該方法邏輯一致。

其他 remove 相關的方法都與 add 方法分析中提到的輔助方法邏輯相似,再也不分析。

get(int index)

public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
複製代碼

node(int 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;
        }
    }
複製代碼

這裏經過索引的位置來肯定從頭結點仍是尾結點開始遍歷,能夠優化一下查詢速度。

set(int index, E element)

public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }
複製代碼

修改操做很簡單,注意會返回原來的值。

總結

LinkedList 內部的數據結構是雙向鏈表

LinkedList 常常拿來與 ArrayList 比較。只要從內部數據結構來分析,就很清楚了。

  • ArrayList 內部是一個動態數組,因此數據的查找是 O(1) 級別,可是數據增刪操做都須要移動元素,均攤複雜度爲:O(n)。
  • LinkedList 內部是一個雙向鏈表,數據的增刪操做只須要從新維護先後結點的關係,複雜度爲 O(1) 級別。可是查找須要從頭結點或尾結點開始遍歷,複雜度爲O(n)。
相關文章
相關標籤/搜索