1. 概述
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
LinkedList是实现了List和Deque接口的双端链表。LinkedList的底层数据结构是链表,不支持随机读取,但是在插入和删除方面会比ArrayList来得更高效。因为LinkedList实现了Deque接口,这也是使它具有双端队列的特性。此外,LinkedList不是线程安全的,如果想要使得LinkedList变成线程安全的,可以调用类Collections中的synchronizedList静态方法:
List list = Collections.synchronizedList(new LinkedList<>());
2. 节点类
LinkedList中链表的节点使用的是Node内部类:
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;
}
}
其实就是我们平常学数据结构的节点类,定义了节点的元素、前驱节点和后继节点。
3. 成员变量
// 节点个数
transient int size = 0;
// 头节点指针
transient Node<E> first;
// 尾节点指针
transient Node<E> last;
成员变量比较少,包括节点个数、头节点指针和尾节点指针。
4. 构造方法
// 空构造方法
public LinkedList() {
}
// Collections参数构造方法
public LinkedList(Collection<? extends E> c) {
this();
addAll(c); // 添加所有元素
}
LinkedList的空构造方法是什么也不干,Collections参数构造方法则会调用addAll方法将元素全部添加到LinkedList中,这个方法我们后面会介绍。
5. add方法
// 指定位置插入元素的add方法
public void add(int index, E element) {
checkPositionIndex(index); // 检查插入位置是否合法
if (index == size) // 如果插入位置是链表的末尾
linkLast(element); // 调用linkLast方法
else
linkBefore(element, node(index)); // 插入位置是链表的中间
}
// 查找index位置的元素
Node<E> node(int index) {
if (index < (size >> 1)) { // 如果index在链表的前半部分
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;
}
}
// 插入元素到链表的尾部
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++;
}
// 插入元素e到succ之前
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev; // succ的前驱节点
final Node<E> newNode = new Node<>(pred, e, succ); // 创建新节点
succ.prev = newNode; // 设置succ的前驱为newNode
if (pred == null) // 如果前驱节点是null,说明succ原来是头节点
first = newNode; // newNode成为新的头节点
else
pred.next = newNode; // 否则,前驱节点的尾节点指向新节点
size++;
modCount++;
}
在指定位置插入元素的add方法,首先是会判断要插入的元素是否是在链表的末尾,如果是的话就直接插入。如果不是插入在链表的末尾,就需要先调用node(index)方法得到原来index位置上的节点,然后再通过linkBefore方法插入到index位置上。
从上面的方法中看到,在LinkedList中完成插入,只需要涉及到index位置上的节点,完全不用移动其他节点,这也是LinkedList的优势,插入非常方便。
6. addAll方法
//在链表末尾插入Collections
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index); // 检查插入位置是否合法
Object[] a = c.toArray(); // 将Collections转换成array
int numNew = a.length; // array元素的个数
if (numNew == 0)
return false;
Node<E> pred, succ; // 定义前驱节点和后继节点
if (index == size) { // 如果插入的位置是链表末尾
succ = null; // 就没有后继节点
pred = last; // 前驱节点就是尾节点
} else {
succ = node(index); // 后继就是index位置的节点
pred = succ.prev; // 前驱就是index位置的前驱节点
}
for (Object o : a) { // 遍历array中的元素
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null); // 创建节点
if (pred == null) // 没有前驱的话
first = newNode; // 就是新的头节点
else
pred.next = newNode; // 否则前驱的后继节点是新的节点
pred = newNode; // 更新前驱
}
if (succ == null) { // 如果没有后继
last = pred;
} else { // 更新后继节点的状态
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
addAll方法中其实和普通的add方法没有什么区别,只是换成了Collections,里面包含多个元素。需要注意的是,方法首先是会将Collections转化成array,因为array具有随机访问的特点,比较方便。
7. remove方法
// 删除指定位置元素的remove方法
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
// 删除指定位置元素
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item; // 节点的元素值
final Node<E> next = x.next; // 后继节点
final Node<E> prev = x.prev; // 前驱节点
if (prev == null) { // 如果没有前驱
first = next; // 后继节点成为新的前驱
} else { // 有前驱
prev.next = next; // 前驱的后继节点指向next
x.prev = null; // gc
}
if (next == null) { // 如果没有后继
last = prev; // 前驱节点成为新的尾节点
} else {
next.prev = prev; // 后继节点的前驱指向prev
x.next = null; // gc
}
x.item = null;
size--;
modCount++;
return element;
}
remove方法也不难,将当前节点的前驱和后继关联起来,然后将当前节点的前驱和后继置为null即可。如下图所示:
8. set方法
public E set(int index, E element) {
checkElementIndex(index); // 检查index范围是否合法
Node<E> x = node(index); // 去的index位置的节点
E oldVal = x.item; // 旧值
x.item = element; // 替换新值
return oldVal;
}
set方法是链表中修改元素的方法,比较简单。
9. peek和getFirst方法
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
peek和getFirst方法都能获取链表的队首元素,区别在于如果队列为空,peek元素不会报错,而getFirst方法会报错。
参考文章:
搞懂 Java LinkedList 源码