源码版本JDK1.8.0_121
LinkedList内部是基于双向链表实现的,元素在内存中的存储并不是连续的,通过节点的引用来关联所有元素。优点和ArrayList相反,添加和删除元素比较快,查询和遍历效率低。
LinkedList类定义
1 | public class LinkedList<E> |
从LinkedList类的定义可以看出:
- 支持泛型
- 继承AbstractSequentialList并实现了List接口,所以是一个有序列表
- 实现了Deque接口,可以作为双端队列操作 (能用作双端队列说明也可作为 队列和栈)
- 实现了Cloneable接口,可以被克隆
- 实现了Serializable接口,并重写了序列化和反序列化方法
Node节点数据结构
1 | private static class Node<E> { |
构造函数和成员变量
1 | // 链表存储元素个数 |
LinkedList有两个构造函数,默认无参构造函数创建一个空链表。带参的构造函数传入一个集合,调用addAll方法将元素插入到链表。
注意: LinkedList的带参构造函数调用this()方法后,有可能有其他线程想链表插入数据,所以集合中的元素并不一定从链表首节点开始。
成员方法
- addAll方法
构造函数中的addAll方法的调用链涉及三个主要函数
1 | public boolean addAll(Collection<? extends E> c) { |
再看其他add方法
add(E e)功能是在尾部添加元素,调用linkLast方法
add(int index, E element)功能是在index插入,同样调用的主要方法有linkLast和linkBefore
1 | // 在末尾插入,实现和上面addAll中的代码相似 |
- 移除方法
移除方法常用有两个,一个是根据index移除,一个是根据object移除
1 | public E remove(int index) { |
此外还有没有任何参数的remove,removeFirst,removeLast方法,其都为Deque接口的实现,后面总结。
从上面分析的增删方法可以看出,基于首尾节点的增删操作都是O(1)复杂度;而非首尾操作要调用node()方法遍历链表,所以平均复杂度是O(n)。
查询get方法有get(index),getFirst(),getLast(),
其中get(index)调用node()方法,经过折半优化;getFirst和getLast为Deque接口实现。序列化
LinkedList自己重写序列化方法的原因和ArrayList一样,为了节省空间。如果将整个LinkedList会把Node节点给写入序列化,由于Node是双端链表的数据节点,会导致多浪费2倍的空间。
1 | private void writeObject(java.io.ObjectOutputStream s) |
- 克隆
LinkedList的克隆也是浅克隆,即只克隆LinkedList并不克隆每个节点。
1 | public Object clone() { |
1 | public interface Deque<E> extends Queue<E> { |
- Deque接口小结:
队列提供的主要操作有offer、poll和peek,双端操作在方法名后加上后缀First和Last。
双端队列的移除方法有remove*和poll*,区别在于遇到null是否报异常;队列的方法同理
双端队列的查询方法有get*和peek*,区别在于遇到null是否报异常;队列的方法element和peek同理
队列和栈的增删操作调用的是addFirst/addLast/removeFirst/removeLast方法,它们又调用LinkedList的核心方法link/linkFirst/linkLast/unlink/unlinkFirst/unlinkLast。
迭代器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31private class ListItr implements ListIterator<E> {
// 前一个遍历过的节点
private Node<E> lastReturned;
// 下次遍历的节点
private Node<E> next;
// 下次遍历的节点下标
private int nextIndex;
private int expectedModCount = modCount;
// 构造函数,指定从index开始遍历, 下标:0<=index<size
ListItr(int index) {
// index等于size时,next节点为null;下标index比size小1,所以nextIndex等于size
next = (index == size) ? null : node(index);
nextIndex = index;
}
public boolean hasNext() {
return nextIndex < size;
}
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
}
ListItr不仅提供从任意节点开始向后变量的功能,也可以向前遍历,移除/修改前次遍历过的元素,在下次遍历指向的next节点前插入元素的功能。