Java集合(4)之 LinkedList 源码解析

213 阅读9分钟

LinkedListArrayList 同样实现了 List 接口,但它基于双向链表实现,插入和删除操作效率较高,而随机访问效率较低。本文通过源码来分析一下 LinkedList 的实现原理,注意事项,使用场景等,以便能更好地使用它(JDK 版本为 1.8)。

LinkedList 的主要特点如下:

  • LinkedListList 接口和 Deque 接口的双向链表实现;
  • LinkedList 实现了列表的所有操作,允许添加 null
  • LinkedList 不是同步的;
  • iterator()listIterator() 返回的迭代器是 fail-fast 的。

定义

先来看一下 LinkedList 的定义:

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

可以看到 LinkedList 继承或实现了以下类或接口:

  • AbstractSequentialListAbstractSequentialList 继承自 AbstractList,但 AbstractSequentialList 只支持按次序访问,而不像 AbstractList 那样支持随机访问。
  • List::实现了 List 接口,提供了所有可选列表操作。
  • Deque:代表双端队列,这是 LinkedList 可用作队列或双端队列的原因。
  • Cloneable:表明其可以被克隆,重写了 clone 方法。
  • java.io.Serializable:表明该类是可以序列化的。

LinkedList 没有实现 RandomAccess,说明 LinkedList 不支持随机访问,这就是 LinkedList 随机访问效率低的原因之一。

属性

LinkedList 的属性主要有:

// 节点个数
transient int size = 0;

// 指向头节点的指针
transient Node<E> first;

// 指向尾节点的指针
transient Node<E> last;

LinkedList 的内部类 Node 表示链表中的节点,包括一个数据域 item,一个后置指针 next,一个前置指针 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;
    }
}

构造方法

LinkedList 中提供了两种构造方法:

构造空链表

public LinkedList() {
}

使用给定 collection 构造链表

构造方法中,先构造一个空链表,再把指定集合 collection 中的所有元素都添加到 LinkedList 中。

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

操作链表的底层方法

下面是几个操作链表的底层方法:

linkFirst 方法

该方法用于在链表头添加元素 e

private void linkFirst(E e) {
    // 使节点 f 指向原来的头节点
    final Node<E> f = first;
    // 新建节点 Node,前驱指针指向 null,后置指针指向原来的头节点
    final Node<E> newNode = new Node<>(null, e, f);
    // 头指针 first 指向新的头节点 newNode
    first = newNode;
    if (f == null)
        // 如果原来的头节点为 null,则更新尾指针
        last = newNode;
    else
        // 否则使原来的头节点 f 的前驱指针指向新的头节点 newNode
        f.prev = newNode;
    size++;
    modCount++;
}

linkLast 方法

该方法用于在链表尾部添加元素 e

void linkLast(E e) {
    // 使节点 l 指向原来的尾节点
    final Node<E> l = last;
    // 新建节点 Node,前驱指针指向 l,后置指针指向 null
    final Node<E> newNode = new Node<>(l, e, null);
    // 尾指针 last 指向新的尾节点 newNode
    last = newNode;
    if (l == null)
        // 如果原来的头节点为 null,则更新头指针
        first = newNode;
    else
        // 否则使原来的尾节点 l 的后置指针指向新的尾节点 newNode
        l.next = newNode;
    size++;
    modCount++;
}

linkBefore 方法

该方法用于在指定节点 succ 之前添加元素 e

void linkBefore(E e, Node<E> succ) {
    // 获得指定节点 succ 的前驱节点
    final Node<E> pred = succ.prev;
    // 新建节点 newNode,前置指针指向 pred,后置指针指向 succ
    final Node<E> newNode = new Node<>(pred, e, succ);
    // succ 的前置指针指向 newNode
    succ.prev = newNode;
    if (pred == null)
        // 如果指定节点的前驱节点为 null,则将 newNode 置为头节点
        first = newNode;
    else
        // 否则更新 pred 的后置节点
        pred.next = newNode;
    size++;
    modCount++;
}

unlinkFirst 方法

该方法用于删除头节点,并返回头节点的值。

private E unlinkFirst(Node<E> f) {
    // 保存头节点的值
    final E element = f.item;
    // 保存头节点的下一个节点
    final Node<E> next = f.next;
    // 头节点的值置为 null
    f.item = null;
    // 头节点的后置指针置为 null
    f.next = null;
    // 将头节点置为 next
    first = next;
    if (next == null)
        // 如果 next 为 null,将尾节点置为 null
        last = null;
    else
        // 否则将 next 的前驱指针指向 null
        next.prev = null;
    size--;
    modCount++;
    return element;
}

unlinkLast 方法

该方法用于删除尾节点,并返回尾节点的值。

private E unlinkLast(Node<E> l) {
    // 保存尾节点的值
    final E element = l.item;
    // 保存尾节点的前一个节点
    final Node<E> prev = l.prev;
    // 尾节点的值置为 null
    l.item = null;
    // 尾节点的前驱指针指向 null
    l.prev = null; // help GC
    // 将尾节点置为 prev
    last = prev;
    if (prev == null)
        // 如果 prev 为 null,将头节点置为 null
        first = null;
    else
        // 否则将 prev 的后置指针指向 null
        prev.next = null;
    size--;
    modCount++;
    return element;
}

unlink 方法

该方法用于删除指定的节点 x

E unlink(Node<E> x) {
    // 保存指定节点的值、前驱节点、后置节点
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;
    
    if (prev == null) {
        // 如果前驱节点为 null,表示删除的是头节点,则将 first 指向为 next
        first = next;
    } else {
        // 否则将 prev 的后置指针指向 next,x 的前置指针指向 null
        prev.next = next;
        x.prev = null;
    }

    if (next == null) {
        // 如果后置节点为 null,表示删除的是尾节点,则将 last 指向 prev
        last = prev;
    } else {
        // 否则将 next 的前置指针指向 prev,x 的后置指针指向 null
        next.prev = prev;
        x.next = null;
    }

    // x 的值置为 null
    x.item = null;
    size--;
    modCount++;
    return element;
}

基本链表方法

add(E) 方法

add 方法在链表的末尾添加指定的元素 e

public boolean add(E e) {
    linkLast(e);
    return true;
}

remove(Object) 方法

remove 方法用于在删除链表中出现的第一个指定的元素 o

public boolean remove(Object o) {
    if (o == null) {
        // 如果 o 为 null,遍历链表,删除第一个值为 null 的节点,返回 true
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        // 否则删除第一个值为 o 的节点。如果链表中存在 o,就返回 true。
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

addAll(int, Collection<? extends E>) 方法

addAll 方法将给定的 collection 集合插入到从 index 位置开始的 List 中。

public boolean addAll(int index, Collection<? extends E> c) {
    // 检查插入到位置是否合法
    checkPositionIndex(index);

    // 将 c 转换为数组
    Object[] a = c.toArray();
    int numNew = a.length;
    if (numNew == 0)   // 如果 c 为空,那么就返回 false
        return false;

    // 使 pred 指向插入点前面的节点,succ 指向插入点后面(pred 的下一个)节点
    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;
}

// 返回索引为 index 位置的节点
Node<E> node(int index) {
    if (index < (size >> 1)) {
        // 如果 index 小于链表的一半,则从表头开始遍历
        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;
    }
}

addAll 方法在链表中间插入元素原理图如下:

add(int, E) 方法

add 方法用于在 List 中索引为 index 位置插入元素 element

public void add(int index, E element) {
    checkPositionIndex(index);
    
    if (index == size)
        // 如果 index 等于 size,则插入到链表尾
        linkLast(element);
    else
        // 否则,插入到索引为 index 位置之前
        linkBefore(element, node(index));
}

set(int, E) 方法

set 方法用给定的元素 element 代替 List 中索引为 index 位置的元素。

public E set(int index, E element) {
    checkElementIndex(index);
    // 返回索引为 index 位置的节点
    Node<E> x = node(index);
    E oldVal = x.item;
    x.item = element;
    return oldVal;
}

get(int) 方法

get 方法会返回 List 中指定位置 index 的元素。

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

indexOf(Ojbect) 方法

index 方法返回 List 中指定元素第一次出现的下标。

public int indexOf(Object o) {
    int index = 0;
    if (o == null) {
        // 如果 o 为 null,遍历链表,查找第一个为 null 的元素,返回 index
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {
        // 否则查找第一个为 Object 的元素,返回 index
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;
}

getFirst/getLast 方法

getFirst 方法返回链表中第一个元素,而 getLast 方法返回链表中最后一个元素。

public E getFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}

public E getLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return l.item;
}

removeFirst/removeLast 方法

removeFirst 方法从 list 中删除第一个元素,并返回它;而 removeLast 方法从 list 中删除最后一个元素,并返回它。

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

addFirst/addLast 方法

addFirst 方法在链表头插入指定元素;addLast 方法在链表尾插入指定元素。

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

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

队列方法

peek/element 方法

peek/element 方法都是返回队列的队首元素。但是,如果队列为空,peek 方法返回 null,而 element 方法会抛出 NoSuchElementException

public E peek() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}

public E element() {
    return getFirst();
}

poll/remove 方法

poll/remvoe 方法都是删除队列的队首元素,并返回。但是如果队列为空,poll 方法会返回空,而 remove 方法会抛出 NoSuchElementException 异常。

public E poll() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}

public E remove() {
    return removeFirst();
}

offer 方法

offer 方法在队列的队尾处添加指定元素。

public boolean offer(E e) {
    return add(e);
}

双端队列方法

offerFirst/offerLast 方法

offerFirst 方法在队列的队首添加元素;而 offerLast 方法在队列的队尾添加元素。

public boolean offerFirst(E e) {
    addFirst(e);
    return true;
}

public boolean offerLast(E e) {
    addLast(e);
    return true;
}

peekFirst/peekLast 方法

peekFirst 方法会返回队列的队首元素,但不删除。如果队列为空,会返回 null

peekLast 方法会返回队列的队尾元素,但不删除。如果队列为空,会返回 null

public E peekFirst() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}

public E peekLast() {
    final Node<E> l = last;
    return (l == null) ? null : l.item;
}

pollFirst/pollLast 方法

pollFirst 方法会删除队列的队首元素,并返回。如果队列为空,则返回 null

pollLast 方法会删除队列的队尾元素,并返回。如果队列为空,则返回 null

public E pollFirst() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}

public E pollLast() {
    final Node<E> l = last;
    return (l == null) ? null : unlinkLast(l);
}

栈方法

push 方法将指定元素压入到栈顶。

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

pop 方法从栈顶弹出元素。

public E pop() {
    return removeFirst();
}