继承体系
LinkedList的类声明如下:
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable{}
可以看到,LinkedList不仅实现了List接口,还实现了Queue和Deque接口,所以它既能作为List使用,也能作为栈/双端队列使用。LinkedList底层使用节点来表达一个元素,而节点本身将会存储上一个节点及下一个节点的引用,成为“双向链表”。
重要属性
// 元素个数
transient int size = 0;
// 链表首节点
transient Node<E> first;
// 链表尾节点
transient Node<E> last;
可以看到,LinkedList的属性还是相对较少的,ArrayList中承担数据承载的是对象数组与size,LinkedList将对象数据换成了链表的首尾节点。通过这两个节点我们就可以遍历链表上的所有数据。
构造方法
public LinkedList() {}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
LinkedList的构造方法较为简单,一个是默认的无参构造器,不做任何事情,另一个是接收集合,其内部调用addAll构建链表
接口方法
List接口方法
List接口方法在集合的基础上引入索引概念,这里其实现是链表,底层并非使用数组进行存储,因此都需要进行遍历链表找到目标元素。
增加元素
list的增加元素有一个典型方法是针对索引进行增加元素。
// 在指定index位置处添加元素
public void add(int index, E element) {
// 判断是否越界
checkPositionIndex(index);
// 如果index是在队列尾节点之后的一个位置
// 把新节点直接添加到尾节点之后
// 否则调用linkBefore()方法在中间添加节点
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
// 寻找index位置的节点
Node<E> node(int index) {
// 因为是双链表
// 所以根据index是在前半段还是后半段决定从前遍历还是从后遍历
// 这样index在后半段的时候可以少遍历一半的元素
if (index < (size >> 1)) {
// 如果是在前半段
// 就从前遍历
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
// 如果是在后半段
// 就从后遍历
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
// 在节点succ之前添加元素
void linkBefore(E e, Node<E> succ) {
// succ是待添加节点的后继节点
// 找到待添加节点的前置节点
final Node<E> pred = succ.prev;
// 在其前置节点和后继节点之间创建一个新节点
final Node<E> newNode = new Node<>(pred, e, succ);
// 修改后继节点的前置指针指向新节点
succ.prev = newNode;
// 判断前置节点是否为空
// 如果为空,说明是第一个添加的元素,修改first指针
// 否则修改前置节点的next为新节点
if (pred == null)
first = newNode;
else
pred.next = newNode;
// 修改元素个数
size++;
// 修改次数加1
modCount++;
}
删除元素
remove帮助我们将链表中的指定对象进行删除,由于是链表存储,需要进行遍历操作,得到其节点,然后调用unlink方法将找到的节点对象从链表内删除
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;
}
// 删除指定节点x
E unlink(Node<E> x) {
// x的元素值
final E element = x.item;
// x的前置节点
final Node<E> next = x.next;
// x的后置节点
final Node<E> prev = x.prev;
// 如果前置节点为空
// 说明是首节点,让first指向x的后置节点
// 否则修改前置节点的next为x的后置节点
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
// 如果后置节点为空
// 说明是尾节点,让last指向x的前置节点
// 否则修改后置节点的prev为x的前置节点
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
// 清空x的元素值,协助GC
x.item = null;
// 元素个数减1
size--;
// 修改次数加1
modCount++;
// 返回删除的元素
return element;
}
同
Queue接口方法
queue接口有以下定义:
// 队列尾部增加元素
boolean add(E e);
// 移除队列头部的元素并返回
E remove();
// 队列尾部增加元素
boolean offer(E e);
// 移除队列头部的元素并返回
E poll();
// 查看队列尾部元素,不移除
E element();
// 查看队列尾部元素,执行移除
E peek();
其中,add,remove,element如果失败会抛出异常,而offer,poll,peek,失败false或者null。
add/offer方法
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
// 得到尾部节点
final Node<E> l = last;
// 创建节点,并关联尾部节点为前置节点
final Node<E> newNode = new Node<>(l, e, null);
// 替换尾部节点为新建节点
last = newNode;
// l == null成立是由于空链表首次增加元素,将first赋值新建的接节点
if (l == null)
first = newNode;
else
// 上一个链表尾节点维护其下一个节点的引用
l.next = newNode;
size++;
modCount++;
}
可以看到add方法内部调用linkLast方法,将新增的元素转换为一个node节点,并连接到链表尾部节点。offer与add的实现逻辑一致
public boolean offer(E e) {
return add(e);
}
remove/poll方法
public E remove() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
// 获得节点值
final E element = f.item;
// 获得节点后续节点
final Node<E> next = f.next;
// 置为null
f.item = null;
f.next = null; // help GC
first = next;
// 如果下一个节点为null则将尾节点置为null
// 否则将下一个节点的前置节点置为null
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
remove方法帮助将队列头节点剔除,最终调用unlinkFirst方法,其内部将节点的next取出作为first节点,解绑后续节点。poll方法与remove方法一致,只是并不会抛出异常
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
element/peek方法
public E element() {
return getFirst();
}
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
获取头结点的值,如果头结点为null,则抛出异常。peek与element功能一致,只是并不会抛出异常:
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
Deque接口方法
Deque实现了Queue,由于Deque是双端队列,因此在Quene的基础上增加了对首尾元素的操作能力:
void addFirst(E e);
void addLast(E e);
boolean offerFirst(E e);
boolean offerLast(E e);
E removeFirst();
E removeLast();
E pollFirst();
E pollLast();
E getFirst();
E getLast();
E peekFirst();
E peekLast();
可以看到,对queue中的每个方法都进行了扩充,产生对first/last的操作。由于代码类似,这里选取部分做解释
addFirst/offerFirst
public void addFirst(E e) {
linkFirst(e);
}
// 从队列首添加元素
private void linkFirst(E e) {
// 首节点
final Node<E> f = first;
// 创建新节点,新节点的next是首节点
final Node<E> newNode = new Node<>(null, e, f);
// 让新节点作为新的首节点
first = newNode;
// 判断是不是第一个添加的元素
// 如果是就把last也置为新节点
// 否则把原首节点的prev指针置为新节点
if (f == null)
last = newNode;
else
f.prev = newNode;
// 元素个数加1
size++;
// 修改次数加1,说明这是一个支持fail-fast的集合
modCount++;
}
removeLast�/pollLast�
removeLast与pollLast功能相似,都是调用unlinkLast进行对尾节点的删除。
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
// 删除尾节点
private E unlinkLast(Node<E> l) {
// 尾节点的元素值
final E element = l.item;
// 尾节点的前置指针
final Node<E> prev = l.prev;
// 清空尾节点的内容,协助GC
l.item = null;
l.prev = null; // help GC
// 让前置节点成为新的尾节点
last = prev;
// 如果只有一个元素,删除了把first置为空
// 否则把前置节点的next置为空
if (prev == null)
first = null;
else
prev.next = null;
// 元素个数减1
size--;
// 修改次数加1
modCount++;
// 返回删除的元素
return element;
}
栈相关api
除此之外,Deque接口还扩充了对栈的操作。可以看到其语义是使用当前类的addFirst与removeFirst实现。
public void push(E e) {
addFirst(e);
}
public E pop() {
return removeFirst();
}
剔除目标节点
Deque中还有一对是新api,表达从链头/未去除第一个相等的节点:
boolean removeFirstOccurrence(Object o);
boolean removeLastOccurrence(Object o);
以removeLastOccurrence举例:
public boolean removeLastOccurrence(Object o) {
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
判定目标对象是否为null,如果为null则遍历链表判断其节点是否为null,匹配则进行unlink操作。如果对象不为null则使用对象的equals方法进行判定操作
总结
LinkedList的能力定义主要来自三个接口:
- List
- Queue
- Deque
List的引入导致需要针对index进行操作,因此需要针对索引号找到对应的节点。这个能力由node(int index)方法提供。Queue表达队列,因此产生对尾部入队头部出队的操作,该操作主要有两套(add, remove, element) 与 (offer,poll,peek),前者在数据不符合条件的时候会抛出异常,后者不会。Deque在Queue的基础上扩充了上述两套的操作,针对头尾新增xxFirst与xxLast的方法。
LinkedList是一个链表,其内部使用Node维护前置节点与后置节点形成双向链表。因此会产生针对列表的一系列操作:
- linkFirst:将一个节点链接到链表头部
- linkLast:将一个节点链接到链表尾部
- unlink(Node node):将一个节点从链表上去除,这个时候会维护当前节点的前置节点与后置节点之间的关系
- unlinkFirst:将头结点从链表上去除,这个时候会维护当前节点的前置节点与后置节点之间的关系
- unlinkLast: 将尾结点从链表上去除,这个时候会维护当前节点的前置节点与后置节点之间的关系
- linkBefore: 主要服务于索引位置建立节点