集合类源码解析——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)。
相关文章
相关标签/搜索