List集合之LinkedList原理剖析一

410 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

LinkedList的基本介绍

LinkedList集合是我们实际工作中使用的最多的集合之一,其同时实现了List和Queue接口,也就是说它同时具备List和Queue两者的特性;

LinkedList集合的主要结构是双向链表,双向链表中的节点不要求有连续的内存存储地址;说起链表,我们大家应该都很熟悉了,其基本结构如下图所示:

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

item表示当前元素,next指向的是下一个节点,prev指向的是上一个节点;

LinkedList使用first表示头节点,使用last表示尾结点,使用size表示当前双向链表的长度;

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

LinkedList集合的添加操作

LinkedList集合的添加操作对外暴露了add(E),addFirst(E),addLast(E),add(int,E),push(E)等等多个方法,但其底层调用的方法基本三个方法可以概括:linkFirst(E),linkLast(E),linkBefore(E e, Node succ),下面我们分别从这三个方法层面进行剖析;

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

我们解析下这段代码:

  1. first代表的是当前LinkedList的第一个元素,因为我们这个方法是linkFirst,所以见名知义,我们要把这个元素放到LinkedList的第一个节点,所以我们先拿到第一个节点;当然这个节点也可能是null;
  2. 将当前元素构建一个Node节点,这个元素要是未来新链表的头部,所以它的上一个节点为null,下一个节点就是我们刚才找到的头部节点first,也就是f
  3. 目前新Node已经融合进链表了,所以新链表的头部也就变成了新Node
  4. 如果之前链表的头部,也就是f是null的话,说明之前是个空链表,换句话说当前新链表也就只有e一个节点;所以说last也应该是e,也就是newNode
  5. 如果之前f不是null的话,说明之前链表是有元素的,但是因为f是first节点,它的上一个元素肯定是null,又因为现在的领头人变成了newNode,所以原来的领头人first就有了新的领头人也就是newNode

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

我们解析下这段代码:

  1. linkLast见名知义就是要把这个元素放到尾部,最后一个节点;所以我们先获取当前节点的last元素
  2. 同样我们构建一个newNode,这个时候e的上一个节点就是l,下一个节点就是null
  3. 因为我们这个newNode是要放到尾部的,所以我们将last赋值为newNode
  4. 同样,如果l为空的话说明之前的list是不含有任何元素的,所以这个时候集合的first,last同时为newNode
  5. 如果l不为null,那么l的下一个节点就是新的尾部节点 newNode;

linkBefore(E, Node)

void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    final Node<E> pred = succ.prev;
    final Node<E> newNode = new Node<>(pred, e, succ);
    succ.prev = newNode;
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;

}

linkBefore意思是在某个指定的节点之前添加;我们解析下这段代码:

  1. 首先我们获取指定节点succ的前一个节点pred
  2. 我们创建一个新的节点,这个节点要在pred和succ之间;
  3. 修改succ的之前的节点引用为当前节点newNode
  4. 如果pred为null的话,说明之前的first为succ,这个时候first就是newNode
  5. 如果pred不为null的话,修改pred的下一个节点为newNode

我们解析了上面三个方法,说是解析,其实也就是翻一下代码啦,作者的整个思路是比较清晰的,我们需要清楚的是对于LinkedList,是不可能存在 first == null && last != null 和 first != null && last == null 这两种情况的;

LinkedList集合的移除操作

LinkedList对外暴露了许多删除的方法,比如说removeLast(),removeFirst(),remove(Object)等等方法,但其实其底层比较关键的方法只有三个,unlinkFirst(Node),unlinkLast(Node),unlink(Node),下面我们分别解析下

unlinkFirst(Node)

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

unlinkFirst见名知义就是删除第一个元素节点,如果删除了第一个元素节点,那么第二个元素节点就会变成新的头节点,下面我们解析下这个方法;

  1. 将f的item赋值给element,element实际是没有什么用的,只是返回使用;
  2. 获取f的next节点,这个就是未来的头节点
  3. 将f的item置为null,是为了方便gc释放element
  4. 将f的next置为null,是为了方便gc释放f自身
  5. next就是新的头节点
  6. 如果next是null,说明之前只有一个元素,现在一个也没有了,所以last也应为null
  7. next不为null,成为了新的头节点,所以它的前一个元素应该是null

unlinkLast(Node)

private E unlinkLast(Node<E> l) {
    // assert l == last && l != null;
    final E element = l.item;
    final Node<E> prev = l.prev;
    l.item = null;
    l.prev = null; // help GC
    last = prev;
    if (prev == null)
        first = null;
    else
        prev.next = null;
    size--;
    modCount++;
    return element;
}

unlinkLast就是为了释放最后一个节点,这个时候unlinkLast的prev就会变成头节点了,这个方法和上面的方法思想都差不多,我们就不解析了,相信聪明的你已经掌握了。

unlink(Node)

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) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }
    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }
    x.item = null;
    size--;
    modCount++;
    return element;
}

unlink这个方法表示的是移除这个元素,然后他的上下节点就联系起来,也就是x.next的prev变成了x.prev,x.prev的next就变成了x.next;这个思想也比较简单;

关于LinkedList我们今天就先说到这里啦,今天周五,大家早点休息,辛苦了一周,希望大家快快乐乐的度过周末,今天我们主要简单介绍了下LinkedList的增删,下一次我们介绍一下查询以及和ArrayList的对比介绍