概要

LinkedList 是一个双向链表,实现了 List 和 Deque 接口。它允许元素为空,非线程安全。

Deque 是一个双端队列,允许2端分别添加和删除元素。JDK中的说明是这样的

继承关系

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

LinkedList 区别于 ArrayList,它是继承自 AbstractSequentialList,并且没有实现 RandomAccess 接口。

如果集合类是 RandomAccess 的实现,尽量用for(int i = 0; i < size; i++) 来遍历而不要用Iterator迭代器来遍历。

反过来,如果List是 Sequence List,则最好用迭代器来进行迭代。后面会看到如果对 LinkedList 使用普通的 for 循环,因为数据结构的原因性能会很差。

AbstractSequentialList 的官方说明如下

AbstractSequentialList 提供了一个顺序访问的集合,想要随机访问的话优先使用 AbstractList 的子类。

内部结构

内部类 全局变量

transient int size = 0;

/**
 * Pointer to first node.
 */
transient Node<E> first;

/**
 * Pointer to last node.
 */
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;
    }
}

一个 Node 内部类,包含数据体本身以及一个指向前面节点的指针和一个指向后面节点的指针。

全局的指向头和尾的 first last 指针。

LinkedList 内部是一个双向链表结构。

构造方法

public LinkedList() {
}

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

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;

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

提供了2个构造方法,无参的构造方法什么事都不做,接收一个 collection 的构造方法将新的集合放到链表的尾端,将 last 指针指向最后一个节点。

add

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

add 方法调用的 linkLast 方法,方法很简单 就是新建了一个node节点放到了链表的最后。

LinkedList大多都是链表操作,代码也比较简单,看下就懂了,下面稍微列出几个方法。

// add first
public void addFirst(E e) {
    linkFirst(e);
}

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

// add last
public void addLast(E e) {
    linkLast(e);
}

private void linkFirst(E e) {
    final Node<E> f = first;
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    size++;
    modCount++;
}

// remove
public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}
private E unlinkFirst(Node<E> f) {
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null;
    f.next = null; // help GC
    first = next;
    if (next == null)
        last = null;
    else
        next.prev = null;
    size--;
    modCount++;
    return element;
}

遍历

如开头说的 AbstractSequentialList 不要使用随机访问的方式遍历,应该采用迭代器或者foreach(foreach 底层是迭代器)。

因为 LinkedList 是个链表,无法像数组一样直接使用下标的方式取到对应的值。

// 随机访问的方式遍历
int size = list.size();
for (int i=0; i<size; i++) {
    list.get(i);
}

看一下它的get方法

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

Node<E> node(int 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;
    }
}

通过 size >> 1 取到一个相对中间的值,然后比较 index 是在前面一段还是后面一段,再按顺序遍历到指定的位置。多此一举。

总结

LinkedList 的大多数API都是基于一个双向链表的操作,循环遍历的时候采用迭代器或者foreach,避免使用普通的for循环。

11-28 01:55