jdk1.8集合源码分析系列-2-LinkedList

192 阅读4分钟

接口继承图以及相关方法分析

老规矩,先看一下整个接口继承图。

这里一些重复的接口我们就不继续分析了,可以看这里的分析。

Queue

public interface Queue<E> extends Collection<E>{
    //添加指定元素,成功时返回true,如果受到容量限制,则抛出IllegalStateException。
    boolean add(E e);  
    //添加指定元素,成功时返回true反之返回false。
    boolean offer(E e);  
    //获取并移除此队列的头。如果此队列为空,则抛出NoSuchElementException。
    E remove();  
    //获取并移除此队列的头,如果此队列为空,则返回 null。
    E poll();  
    //获取但不移除此队列的头。如果此队列为空,则抛出NoSuchElementException。
    E element();  
    //获取但不移除此队列的头;如果此队列为空,则返回 null。
    E peek();  
}  

这个接口封装着队列接口的常见操作,但是并没有定义队列阻塞的相关方法,这些方法被定义在BlockingQueue接口里面。

仔细观察这些方法,我们可以做一个简单的分类如下:

方法抛出异常返回特殊值
插入add(e)offer(e)
移除remove()poll()
检查element()peek()

Deque

public interface Deque<E> extends Queue<E> {
    //addXXX,removeXXX,peekXXX等方法方法我们不做解释了,和上面基本一致。
    //比如addFirst把e元素使用add方法加到头部。
    //理解了add方法就理解了addFirst方法
    void addFirst(E e);  
    void addLast(E e);  
    boolean offerFirst(E e);  
    boolean offerLast(E e);  
    E removeFirst();  
    E removeLast();  
    E pollFirst();  
    E pollLast();  
    E getFirst();  
    E getLast();  
    E peekFirst();  
    E peekLast();  
    //从此双端队列移除第一次出现的指定元素。
    boolean removeFirstOccurrence(Object o);  
    //从此双端队列移除最后一次出现的指定元素。
    boolean removeLastOccurrence(Object o);  
    //将一个元素推入此双端队列所表示的堆栈(换句话说,此双端队列的头部)。
    //成功返回 true,如果超过容量限制,则抛出 IllegalStateException。
    void push(E e);  
    //从此双端队列所表示的堆栈中弹出一个元素。
    E pop();  
    //获得一个逆序的迭代器
    Iterator<E> descendingIterator();  
}  

deque 是“double ended queue”的缩写,代表双端队列的意思,所以我们可以看到这些接口的设计就是把双端队列的一些共性方法抽象出来。这里我们也做一个分类说明一下:

第一个元素(头部) 最后一个元素(尾部)
方法 抛出异常 特殊值 抛出异常 特殊值
插入 addFirst(e) offerFirst(e) addLast(e) offerLast(e)
移除 removeFirst() pollFirst() removeLast() pollLast()
检查 getFirst() peekFirst() getLast() peekLast()

AbstractSequentialList

就像文档说的,这个类在AbstractList是为“连续访问”类型的数据结构(比如linkedList)服务的,它最大程度减少你实现“支持连续访问”的数据结构的成本。注意:对于随机访问数据(如数组),应该优先使用 AbstractList,而不是先使用此类。

看源码你可以发现get、set、add、remove、addAll、iterator方法都已经被实现了。你只需要实现这个方法。

public abstract ListIterator<E> listIterator(int index);

LinkedList源码分析

先看一下解数据结构

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    //链表长度
    transient int size = 0;
    /**
     * 头部节点指针
     */
    transient Node<E> first;
    /**
     * 尾部节点指针
     */
    transient Node<E> last;
    //modCount,结构性修改的次数,和ArrayList里面作用一致。
    //可以看https://juejin.cn/post/6847902216590721037的分析。
    protected transient int modCount = 0;
    
    //Node结构
    private static class Node<E> {
        //节点值
        E item;
        //下一个节点指针
        Node<E> next;
        //前一个节点指针
        Node<E> prev;
    }
}

从数据结构可以看出,这是一个典型的双向链表,并且包含头尾指针,这里我们简单的回顾一下双向链表(这个图没有画头尾指针)的结构和常见操作的复杂度。

遍历逻辑

前面讲AbstractSequentialList说到了遍历的方法最终调用的是这个方法listIterator,我们来看下这个方法的实现。

//"连续访问"需要实现这个方法
public ListIterator<E> listIterator(int index) {
    checkPositionIndex(index);
    return new ListItr(index);
}

//调用ListItr构造方法
private class ListItr implements ListIterator<E> {
    private Node<E> lastReturned;
    private Node<E> next;
    private int nextIndex;
    private int expectedModCount = modCount;

    ListItr(int index) {
        // assert isPositionIndex(index);
        // 边界校验通过的情况下返回的是node(index)
        next = (index == size) ? null : node(index);
        nextIndex = index;
    }
}

//调用node方法
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;
    }
}

添加方法

这个方法的实现可以结合上面画的数据结构图一起分析。 删除方法就不描述了,实现是类似的。

//添加
public void add(int index, E element) {
    //边界检查
    checkPositionIndex(index);
    
    if (index == size)
        //如果是加到最后一个
        linkLast(element);
    else
        //往中间插入
        linkBefore(element, node(index));
}

void linkBefore(E e, Node<E> succ) {
    final Node<E> pred = succ.prev;
    //创建一个新的node,把这个新node的前后指针分别连接上pred和succ。
    final Node<E> newNode = new Node<>(pred, e, succ);
    
    //把后一个节点的前指针连接上这个新节点。
    succ.prev = newNode;
    
    //把前一个节点的后指针连接上这个新节点
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    
    //长度+1
    size++;
    //结构性修改次数+1
    modCount++;
}

关于LinkedList的题

LinkedList是并发安全的么?

显然不是,内部没有并发安全的保证。关键变量没有可见,原子,有序的保证。

LinkedList的存储性能如何?

遍历复杂度o(n),插入和删除复杂度都是o(1)。