从零开始的JAVA生活(3)LinkedList源码与解析

252 阅读3分钟

概述

LinkedList同时实现了ListDeque接口(Deque接口又继承了queue接口)也就是说它既可以看作一个顺序容器,又可以看作一个队列(Queue),同时又可以看作一个栈(Stack)。当你需要使用栈或者队列时,可以考虑使用LinkedList,一方面是因为Java官方已经声明不建议使用Stack类,更遗憾的是,Java里根本没有一个叫做Queue的类(它是个接口名字)。关于栈或队列,现在的首选是ArrayDeque,它有着比LinkedList(当作栈或队列使用时)有着更好的性能。

LinkedList的实现方式决定了所有跟下标相关的操作都是线性时间,而在首段或者末尾删除元素只需要常数时间。为追求效率LinkedList*没有实现同步(synchronized),如果需要多个线程并发访问,可以先采用Collections.synchronizedList()方法对其进行包装。也就是说ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;

LinkedList实现

底层通过双向链表实现,本节将着重讲解插入和删除元素时双向链表的维护过程,也即是之间解跟List接口相关的函数,而将QueueStack以及Deque相关的知识放在下一节讲。双向链表的每个节点用内部类Node表示。LinkedList通过firstlast引用分别指向链表的第一个和最后一个元素。注意这里没有所谓的哑元,当链表为空的时候firstlast都指向null。需要注意的是在JDK1.6以前,LinkedList还是一个循环链表,后来才取消。

不仅如此,由于LinkedList仅仅只有头和尾两个节点,所以当我们需要查找某个元素的时候,我们还需要O(n)的时间,正因为如此,在增删改的时候我们也大多需要O(n)来进行查找。

源码分析

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

LinkedList的源码并没有什么特别值得提的,

  • 添加:add,linkLast,LinkFirst 注意要先进行下标越界检查
  • 获取:getFirst,getLast,get, 注意这里的get其实是调用node方法,从这个方法我们可以看出,此方法通过比较索引值和size的一半从而确定是从头开始还是从尾开始的便利,这样充分利用了双向链表的特性
  • 删除:removeFirst,removeLast,remove删除首次出现的指定元素,注意有返回值,remove(ind),clear 注意这里remove调用的其实是unlinked方法
E unlink(Node<E> x) {
    // 断言 x 不为 null
    // assert x != null;
    // 获取当前节点(也就是待删除节点)的元素
    final E element = x.item;
    // 获取当前节点的下一个节点
    final Node<E> next = x.next;
    // 获取当前节点的前一个节点
    final Node<E> prev = x.prev;

    // 如果前一个节点为空,则说明当前节点是头节点
    if (prev == null) {
        // 直接让链表头指向当前节点的下一个节点
        first = next;
    } else { // 如果前一个节点不为空
        // 将前一个节点的 next 指针指向当前节点的下一个节点
        prev.next = next;
        // 将当前节点的 prev 指针置为 null,,方便 GC 回收
        x.prev = null;
    }

    // 如果下一个节点为空,则说明当前节点是尾节点
    if (next == null) {
        // 直接让链表尾指向当前节点的前一个节点
        last = prev;
    } else { // 如果下一个节点不为空
        // 将下一个节点的 prev 指针指向当前节点的前一个节点
        next.prev = prev;
        // 将当前节点的 next 指针置为 null,方便 GC 回收
        x.next = null;
    }

    // 将当前节点元素置为 null,方便 GC 回收
    x.item = null;
    size--;
    modCount++;
    return element;
}

Stack & Queue概述

Java里有一个叫做Stack的类,却没有叫做Queue的类(它是个接口名字)。当需要使用栈时,Java已不推荐使用Stack,而是推荐使用更高效的ArrayDeque;既然Queue只是一个接口,当需要使用队列时也就首选ArrayDeque了(次选是LinkedList)
这里借用一下pdai大哥做的图

image.png

image.png