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