LinkedList源码解析

LinkedList集合同时具有List集合和Queue集合的基本特征。

在这里插入图片描述

通过上面继承体系可以知道,LinkedList同时实现了List接口和Queue接口,这两个接口分别代表了JCF中三大集合结构(List、Queue、Set)中的两个。在JDK1.2版本后,JDK1.6版本前,官方推荐使用LinkedList集合模拟栈结构

LinkedList集合的主要结构是双向链表。双向链表中的节点把要求有连续的内存存储地址,因此在向双向链表中插入新节点的时候,无须申请一块连续的存储空间,只需按需申请存储空间,LinkedList集合中的链表的每个节点都使用一个java.util.LinkedList.Node类的对象进行描述

private static class Node<E> {
    //主要用于在当前Node节点上的存储的数据对象
    E item;
    //类型也是Node,表示当前节点指向的下一个节点
    Node<E> next;
    //类型也是Node,表示当前节点指向的上一个节点
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = 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;

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    
    //记录当前双向链表的头节点
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    
    //记录当前双向链表的尾节点
    transient Node<E> last;

    /**
     * Constructs an empty list.
     */
    public LinkedList() {
    }

first == null && last == null:当双向链表没有任何数据对象的时候,first属性和last属性都为null

first == last:当双向链表只有一个对象的时候,first属性和last属性一定指向同一个节点

first != null && last != null:当双向链表至少有一个数据对象的时候,first属性和last属性都不可能为null

LinkedList集合内部场景是不可能出现(first != null && last == null)或者(first == null && last != null)。因为first属性和last属性要么都为null,要么都不为null

JDK1.8开始,LinkedList集合中有3个用于在链表的不同位置添加新的节点,分别是linkFirst(E)方法,linkLast(E)方法和linkBefore(E,Node)方法

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

这些方法都不是public修饰的方法,这些方法要通过add(E)、addLast(E)、addFirst(E)等方法进行封装,才能对外提供服务

public boolean add(E e) {
    linkLast(e);
    return true;
}
public void addLast(E e) {
    linkLast(e);
}
public void addFirst(E e) {
    linkFirst(e);
}

首先看一下linkFirst(E)方法,linkFirst(E)方法可以在双向链表的头部添加一个新的Node节点,用于存储新添加的数据对象,并且调整当前first属性的指向位置

/**
 * Links e as first element.
 */
private void linkFirst(E e) {
    //使用一个临时的变量记录操作前first属性的信息
    final Node<E> f = first;
    //创建一个数据信息为e的新节点,该节点前置节点引用为null,后置节点引用指向原先的头节点
    final Node<E> newNode = new Node<>(null, e, f);
    //因为要在双向链表头部添加新的节点,将first属性中的信息重新设置
    first = newNode;
    //条件成立,说明双向链表没有任何节点
    if (f == null)
        //将last节点也指向新的节点,这样first和last节点属性同时指向同一个节点
        last = newNode;
    else
        //不成立,说明双向链表至少有一个节点,只需要把原来的头节点的前置节点引用指向新的头节点
        f.prev = newNode;
    //双向链表长度 + 1
    size++;
    //linkedList集合的操作次数 + 1
    modCount++;
}

linkLast(E)方法可以在当前双向链表的尾节点之后添加一个新的节点,并且调整last属性的指向位置

void linkLast(E e) {
    //使用一个临时变量来记录操作前的last属性信息
    final Node<E> l = last;
    //创建一个新的节点,item属性值为e,新节点的前置对象指向原来的尾节点,后置节点为null
    final Node<E> newNode = new Node<>(l, e, null);
    //因为要在双向链表的尾节点添加新的节点,将last属性中的信息重新设置
    last = newNode;
    //条件成立,说明双向链表没有任何节点
    if (l == null)
        //将first节点指向新的节点,first和last都同时指向同一个节点
        first = newNode;
    else
        //不成立,双向链表至少有一个节点,将原来的尾节点的后置节点指向新的尾节点
        l.next = newNode;
    //双向链表长度 + 1 
    size++;
    //linkedList集合的操作次数 + 1
    modCount++;
}

linkBefore(E,Node) 方法可以在指定的节点前的索引位置上插入一个新的节点,注意的是,LinkedList集合的操作逻辑可以保证这里的succ入参一定不为null,并且一定已经存储于当前LinkedList集合中的某个位置

void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    //创建一个变量,记录当前succ的前置节点引用(可能为null)
    final Node<E> pred = succ.prev;
    //创建一个新的节点,该节点的前置节点引用指向succ节点的前置节点,该节点的后置节点引用指向succ节点
    final Node<E> newNode = new Node<>(pred, e, succ);
    //将succ节点的前置节点重新设置为刚刚创建的新的节点
    succ.prev = newNode;
    //条件成立,说明当前succ节点原本就是双向链表的头节点,可以看作当前的操作其实就是在链表的头部添加一个新的节点
    if (pred == null)
        //这个时候将first属性的指向新创建的节点
        first = newNode;
    else
        //不成立,将succ的前置节点的后置节点设置为当前新创建的节点
        pred.next = newNode;
    //双向链表长度 + 1
    size++;
    //linkedList集合的操作次数 + 1
    modCount++;
}

LinkedList集合中有3个移除集合中数据对象的方法,unlinkFirst(Node)方法、unlinkLast(Node)方法、unlink(Node)方法,LinkedList集合中提过了3个对外封装好的方法removeFirst()、removeLast()、remove(Object)

public boolean remove(Object o) {
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}
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);
}

使用unlinkFirst(Node)方法可以移除LinkedList集合中双向链表的头节点,并且重新设置它的后置节点为新的头节点,该方法入参 f 就是当前双向链表的头节点,该参数一定不为null

private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    //定义一个element变量,记录当前双向链表头节点的数据对象,以便方法最后将其返回
    final E element = f.item;
    //创建一个next变量,记录当前双向链表头节点的后置节点引用,可能为null
    final Node<E> next = f.next;
    //设置当前双向链表头节点的数据对象为null,后置节点引用设置为null
    f.item = null;
    f.next = null; // help GC  (帮助进行GC(java的垃圾回收机制))
    //设置双向链表新的头节点为当前头节点的后置节点
    first = next;
    //条件处理,说明完成头节点的移除操作,当前双向链表已经没有任何节点
    if (next == null)
        //将last属性设置为null
        last = null;
    else
        //不成立,设置新的头节点前置节点设置为null,因为新的头节点的前置节点指向是原先的头节点
        next.prev = null;
    //双向链表长度 - 1
    size--;
    //LinkedList集合操作次数 + 1 
    modCount++;
    return element;
}

unlinkLast(Node)方法可以移除LinkedList集合中双向链表的尾节点,并且重新设置它的前置节点为新的尾节点,该方法入参 l 就是当前双向链表的尾节点,该参数一定不为null

private E unlinkLast(Node<E> l) {
    // assert l == last && l != null;
    //定义一个element变量记录当前双向链表尾节点中的数据对象,以便在最后将其返回
    final E element = l.item;
    //创建一个prev变量,记录当前双向链表尾节点中的前置节点,该变量可能为null
    final Node<E> prev = l.prev;
    //设置当前双向链表尾节点中的数据为null,前置对象引用为null
    l.item = null;
    l.prev = null; // help GC  帮助进行GC(垃圾回收)
    //设置双向链表新的尾节点为当前尾节点的前置节点
    last = prev;
    //条件处理,说明移除完尾节点之后双向链表已经没有任何节点了
    if (prev == null)
        //设置头节点为null
        first = null;
    else
        //不成立,设置新的尾节点后置节点设置为null,因为新的尾节点的后置节点指向是原先的尾节点
        prev.next = null;
    //双向链表长度 - 1
    size--;
    //LinkedList集合操作次数 + 1
    modCount++;
    return element;
}

unlink(Node)方法可以从双向链表中移除指定的节点,其入参x所指向的节点一定位于双向链表中

E unlink(Node<E> x) {
    // assert x != null;
    //定义一个element变量,记录当前节点中的数据对象,以便方法最后返回
    final E element = x.item;
    //创建一个next节点,记录当前节点中的后置节点引用,可能为null
    final Node<E> next = x.next;
    //创建一个prev节点,记录当前节点中的前置节点引用,可能为null
    final Node<E> prev = x.prev;

    //如果条件成立,说明被移除的x节点是双向链表的头节点
    if (prev == null) {
        //将x的后置节点设置为新的头节点
        first = next;
    } else {
        //将x的前置节点中的后置节点设置为移除的x节点的后置节点
        prev.next = next;
        //将移除的x节点的前置节点设置为null
        x.prev = null;
    }

    //如果条件成立,说明被移除的x节点是双向链表的尾节点
    if (next == null) {
        //将移除的x的节点的前置节点设置为新的尾节点
        last = prev;
    } else {
        //将x的后置节点中的前置节点设置为移除x节点的前置节点
        next.prev = prev;
        //将移除的x节点的后置节点设置为null
        x.next = null;
    }

    //将移除的x节点中的数据对象设置为null
    x.item = null;
    //双向链表长度 - 1
    size--;
    //LinkedList集合操作次数 + 1
    modCount++;
    return element;
}

LinkedList集合中的其他方法调用linkBefore(E,Node)方法插入新的节点或者调用unlink(Node)方法移除指定的节点的时候,都需要先找到这个要被操作的节点,由于双向链表的构造结构,LinkedList集合不能跟ArrayList集合一样,通过指定的一个数值便可以定位到指定的索引位,双向链表查询指定的索引位的方式,就是从头节点或者尾节点开始进行遍历,实现的方法在java.util.LinkedList集合中的node(int)方法

node方法返回的结果不为null,入参index为当前操作要查询的节点索引值,开始index值为0

Node<E> node(int index) {
    // assert isElementIndex(index);

    //如果条件成立,说明当前指定的index号索引位在当前双向链表的前半段
    if (index < (size >> 1)) {
        //从当前双向链表的头节点开始向后依次查询
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        //不成立,说明当前指定的index号索引位在双向链表的后半段,从当前双向链表的尾节点开始向后依次查询
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

node(int)方法在LinkedList集合中的使用具有普遍性,addAll(int,Collection)方法,get(int)方法,set(int,E)方法,add(int,E)方法,remove(int)方法等的读或写操作都使用node(int)方法查询双向链表中的指定的索引位

有一些方法比较特殊,如indexOf(Object)方法,lastIndexOf(Object)方法,remove(Object)方法,这种方法不是使用node(int)方法查询索引位,它们使用的数据对象引用信息来查询指定的索引位,以indexOf(Object)为例子,源码如下

indexOf(Object)方法主要用于双向链表的头节点开始,查询离0号索引位最近的一个节点,这个节点的特点是,item属性存储的数据信息(引用地址)等于当前入参o所代表的数据信息(引用地址),该方法规定,如果当前入参o为null,则查询离头节点最近的item属性为null的节点,如果没有找到符合条件的节点,该方法返回-1

public int indexOf(Object o) {
    int index = 0;
    //如果入参o为null
    if (o == null) {
        //从头节点开始向后遍历,直到找到某个item属性值为null的节点
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {
        //不为null,从头节点开始向后遍历,直到找到某个item属性值向的内存地址和传入参数o指向的内存地址相同的节点
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值