Java集合List系列(二):LinkedList源码解析(JDK1.8)

图1-1 思维导图

概述

LinkedList是基于节点实现的 双向 链表的 List,每个节点都指向前一个和后一个节点从形成链表。

类图

图 1-2 LinkedList类图

从图中我们可以看出, LinkedList实现了4个接口和继承了1个抽象类,4个接口分别为:

  • List接口,主要提供添加、删除、修改、迭代遍历等操作。

  • Cloneable接口,代表 LinkedList支持克隆。

  • Serializable接口,表示ArrayList支持序列化的功能。

  • Deque接口,提供 双端 队列的功能,LinkedList支持快速的在头尾添加和读取元素。

LinedList所实现的接口相比于 ArrayList来说,少了一个 RandomAccess接口,说明 LinkedList并不支持随机访问;同时多了一个 Deque接口,新增了双端队列的能力。

继承的抽象类为 AbstactSequentialList,它是 AbstractList的子类,实现了本来只能 顺序 访问的数据存储结构(例如:链表)的 get(int index)、 add(int index, E element)随机 操作的方法。这句话可能有点绕,稍加解释一番:链表是一种只能顺序访问的数据存储结构,而 AbstractSequentialList抽象类对这类只能 顺序访问/操作 的数据存储结构,也提供了类数组般的随机访问/操作API。感兴趣的朋友可以去看看 AbstractSequentialList的源代码,其是基于迭代器顺序遍历(说到底还是需要遍历,只不过是它帮我们做了这一步~)实现的。

不过需要留意的是,LinkedListLinkedList一般,都结合了自身的特性大量重写了抽象类提供的方法实现;ArrayList重写了 AbstractList提供的方法实现, LinkedList重写了 AbstactSequentialList提供的方法实现。

一般情况下,对于支持随机访问的数据结构会继承 AbstractList抽象类,不支持随机访问的则会继承 AbstactSequentialList抽象类。

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

属性

LinkedList只有 3 个属性,分别为: 头节点 first、 尾节点 last以及代表数量的 size。三者的关系如下图所示。

图1-3 属性关系图

相信朋友们都接触过双向链表的概念,这里就不做过多的展开解释了。

具体源码如下:

/**
  * 头节点
  */
transient Node<E> first;

/**
  * 尾节点
*/
transient Node<E> last;

/**
  * 数量
  */
transient int size = 0;

/**
  * 底层实现是 双向链表
  */
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;
    }
}

构造方法

LinkedList的构造方法只有2个:无参构造方法 public ListedList()和指定集合的 public LinkedList(Collection<? extends E> c),具体代码如下:


/**
  * 无参构造方法
  */
public LinkedList() {
   
}

/**
  * 指定添加集合c的构造方法
  * @param c
  */
public LinkedList(Collection<? extends E> c) {
   
    this();
    // 批量添加元素,后面在新增元素模块会详细介绍
    addAll(c);
}

相比于 ArrayList来说, LinkedList没有类似指定初始化容量的构造方法;造成这个区别的原因还是因为两者的底层实现方式不一样,LinkedList是基于节点实现的双向链表,所以每次添加元素的时候直接 new一个节点即可。

新增元素

新增元素的方法主要有如下4个,分别为:

  • public boolean add(E e)在链表节点后 顺序 添加一个元素

  • public void add(int index, E element)在指定 index位置添加元素 element

  • public boolean addAll(Collection<? extends E> c)在链表末尾 顺序 添加多个元素

  • public boolean addAll(int index, Collection<? extends E> c)在指定 index位置添加多个元素

严格来说,LinkedList的新增元素的方法还有另外 2 个,分别为: addFirst(E e)addLast(E e);这两个方法是 LinkedList作为实现双端队列 Deque的产物,后面会对 LinkedList作为双端队列的方法做专门的介绍。

public boolean add(E e)

继承于 AbstractList抽象类,在链表节点后 顺序 添加一个元素,源码如下:

/**
  * 在链表节点后顺序添加一个元素e
  * @param e
  * @return boolean 添加成功返回true
  */
@Override
public boolean add(E e) {
   
  // 顺序添加单个元素到链表的末尾
  linkLast(e);
  return true;
}

/**
  * 真实执行添加操作(往链表最后添加新的元素)
  * @param e 添加元素 e
  */
void linkLast(E e) {
   
  // 记录原 last 节点
  final Node<E> l = last;
  // Node类构造方法三个参数: Node<E> prev, E element, Node<E> next
  // 所以也很好理解,新节点的前置pre节点是原来的last节点, e代表元素本身 新节点的next节点为null(不存在新的节点)
  final Node<E> newNode = new Node<>(l, e, null);
  // 更新last节点为最新的newNode
  last = newNode;
  // 双向链表,相互关联---新节点关联原last节点,原last节点也要关联新的节点
  if (l == null) {
   
      // l == null,说明是一个空的情况,上面已经将last指向新节点了,需要将first也指向新节点
      first = newNode;
  } else {
   
      // 不为空,则说明链表中已有元素,直接将last节点和new节点做一个双向的绑定关系即可
      l.next = newNode;
  }
  size++;
  // 操作的次数++,全程似乎就在序列化和非序列化模块有瞅到这个东东被使用到
  modCount++;
}

代码理解起来不难,往链表末尾新增一个新的节点,可分3步走:

  1. 创建新节点 new Node<>(last, e, null),参数 1 为 last是说新节点的前置节点是原last节点,参数2 代表元素本身e;参数3 为 null说明新节点后面无其他节点

  2. 原last指针更新为新节点,这里分2种情况:

    1. 原last节点为空,说明链表还未存储过元素,这时候需要将first节点也指向新节点

    2. 原last节点不为空,说明链表已存储过元素,直接将原last节点的后置指针 next指向新节点(因为其为双向链表)

  3. 更新 size和 操作次数 modCount

public void add(int index, E element)

继承于 AbstractSequentialList抽象类,为以双向链表为底层存储结构的 LinkedList赋予了类数组般随机访问的能力,具体代码如下:

 /**
  * 添加单个元素到指定位置
  * @param index 下标
  * @param element 元素本身
  */
@Override
public void add(int index, E element) {
   
    // 参数index 有效性检查
    checkPositionIndex(index);

    // index从0开始,所以当index == size 说明是最后的位置,也即是如上面的在链表最后新增元素一样
    if (index == size) {
   
        linkLast(element);
    } else {
   
        // node(index) 获取index位置的node节点
        linkBefore(element, node(index));
    }
}
/**
  * 添加元素e到 succ节点的前面
  *
  * pred ---> succ     =====变成===>        pred --->newNode ---> succ
  *
  * 步骤:
  *  1.获取succ节点的前一个节点 pred
  *  2.创建新的待插入节点newNode,指定其前置节点(pred)和后置节点(succ)
  *  3.更新succ的前置节点为新节点newNode(双向绑定的体现)
  *  4.更新pred的后置节点为新节点newNode(双向绑定的体现)
  *      4.1如果pred为空,说明succ本身为头节点,换句话说相当于是在头节点之前插入新的节点,即更新first = newNode即可
  *      4.2如果pred不为空,则正常的pred.next= newNoe即可
  * @param e
  * @param succ
  */
void linkBefore(E e, Node<E> succ) {
   
    // 获取succ节点的前一个节点(因为方法的逻辑是:添加元素e到succ节点的前面)
    final Node<E> pred = succ.prev;
    // 创建新节点,并且本身关联前后节点(前节点: pred、 后节点:succ)
    final Node<E> newNode = new Node<>(pred, e, succ);
    // 双向绑定之画个图完事 succ.prev指向新的节点(在其前面)
    succ.prev = newNode;
    // 如果pred为空,说明succ本身就是为头节点,则更新first指向newNode作为新的首节点
    if (pred == null) {
   
        first = newNode;
    } else {
   
        // 直接双向绑定
        pred.next = newNode;
    }
    size++;
    modCount++;
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值