- LinkedList是实现了List和Deque接口的双向链表。实现了所有的可选列表操作(add, remove, set, get, size, indexOf, contains …),允许所有的元素,包括null。
- 对链表的索引操作将从链表头或尾进行遍历,以较接近索引为准。
- LinkedList是线程不安全的,根据需要可以使用集合类的相应方法包装(底层使用synchronized关键字);listIterator和iterator方法返回的迭代器具有fast-fail机制。这两点和ArrayList差不多。
1. 继承关系
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
2. 变量
transient int size = 0;
/**
* 链表第一个节点
* 始终存在: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* 链表最后一个节点
* 始终存在: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
Node为LinkedList一个内部类
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;
}
}
可以看到Node的实现非常简单,万物皆对象,一个链表结构就这么抽象出来了。(ArrayList底层维护的是一个数组,那LinkedList底层维护的就是一个个节点Node)
再回到定义的first和last这两个属性,根据它们始终存在的关系,可以得出两条规律:
- 当LinkedList的size == 0,有first == null && last == null。
- 当LinkedList的size >= 1,有first.prev == null && last.next == null。作为特例,当size == 1,有first == last。
我们将在后面具体方法实现中看到这种逻辑。
3. 方法
此处仅选取一个具有代表性的方法进行分析,想要搞清楚更多细节,除了亲自阅读源码,别无他法。
- void add(int index, E element)
/**
* 在列表的指定位置插入指定元素。
* 将当前位于该位置的元素(如果有)和任何后续元素向右移动(将其索引加1)。
*
*/
public void add(int index, E element) {
checkPositionIndex(index); // 需满足:index <= size。
if (index == size)
linkLast(element); // 此时大致相当于add()方法,在链表末端添加元素。
else
linkBefore(element, node(index));
}
/**
* 返回指定索引的节点(非空)
*/
Node<E> node(int index) {
// assert isElementIndex(index);
// 此处即LinkedList做为双向链表的一个体现。
// 如果index小于size/2,就从链表头开始遍历,否则就从链表尾进行遍历。
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;
}
}
/**
* 在链表末端添加元素e
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null); // 新建节点newNode保存元素e,其前一节点为last,后一节点为null。
last = newNode;
// 如果添加元素之前last为null(根据始终存在,first也为null)。
// 那么添加元素后链表只有一个节点,它既代表last又代表first。
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
/**
* 在非空节点succ之前插入元素e
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
// 先记录succ的前节点prev,然后在succ和prev之间构造一个新节点newNode。
// 然后再处理succ和prev这两个节点前后的连接关系。(即断开链表,再建立新的连接关系)
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++;
}
类似linkLast和linkBefore的方法,还有:linkFirst、unlinkFirst、unlinkLast、unlink(访问类型为default或者private),它们封装了对链表的基本操作,方便其他方法直接调用。
关键是理解Node的抽象表达,能在脑海中建立这样一条双向链表的模型。
另外,LinkedList还实现了Deque接口。提供了peek、poll、offer、push、pop等方法(具体实现也是调用之前的linkLast之类的方法)。有了这些方法,就可以对LinkedList建立另外一种抽象,即双端队列。