概述
LinkedList
是基于节点实现的 双向 链表的 List,每个节点都指向前一个和后一个节点从形成链表。
类图
从图中我们可以看出, 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
的源代码,其是基于迭代器顺序遍历(说到底还是需要遍历,只不过是它帮我们做了这一步~)实现的。
不过需要留意的是,LinkedList
和 LinkedList
一般,都结合了自身的特性大量重写了抽象类提供的方法实现;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
。三者的关系如下图所示。
相信朋友们都接触过双向链表的概念,这里就不做过多的展开解释了。
具体源码如下:
/**
* 头节点
*/
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步走:
-
创建新节点
new Node<>(last, e, null)
,参数 1 为last
是说新节点的前置节点是原last节点,参数2 代表元素本身e;参数3 为null
说明新节点后面无其他节点 -
原last指针更新为新节点,这里分2种情况:
-
原last节点为空,说明链表还未存储过元素,这时候需要将first节点也指向新节点
-
原last节点不为空,说明链表已存储过元素,直接将原last节点的后置指针
next
指向新节点(因为其为双向链表)
-
-
更新
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++;