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是LinkedList的静态内部类,包含数据item、指向前一个结点的引用prev以及指向后一个结点的引用next,从这一点看,是比较常规的做法。
一、属性
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;
size是链表的大小、first是第一个结点、last是最后一个结点
那么判空也就有几种方法:
(1) size == 0
(2)first == null
(3)last == null
二、构造函数
1.无参构造
public LinkedList() {
}
2.有参构造
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
实话实说,我没有用过有参构造,都是用无参构造
三、数据存取
既然是双向链表,我觉得应该关注的是:
(1)从头插入数据
(2)从头取出数据
(3)从尾插入数据
(4)从尾取出数据
一个个看:
1. 从头插入数据,让新插入的结点成为第一个结点,说白了就是头插法了
/**
* Links e as first element.
*/
private void linkFirst(E e) {
// 第一个结点
final Node<E> f = first;
// 新结点,并且设置了新节点的下一个结点为当前的第一个结点first
final Node<E> newNode = new Node<>(null, e, f);
// 让新结点成为新的第一个结点
first = newNode;
// 如果链表为空,则让第一个结点和最后一个结点都指向新结点
if (f == null)
last = newNode;
else
f.prev = newNode; // 否则,设置旧的第一个结点的prev为新结点
size++; // 结点个数加1
modCount++; // 修改次数加1
}
2. 从头取出数据
/**
* Unlinks non-null first node f.
*/
private E unlinkFirst(Node<E> f) {
// f是第一个结点,且不为空
// assert f == first && f != null;
// 取出结点值
final E element = f.item;
// 得到第二个结点
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
// 让第二个结点成为第一个结点
first = next;
// 如果第二个结点为空,则整个链表已经为空了
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
3. 从尾插入数据,让新结点成为新的尾结点,其实就是尾插法
/**
* Links e as last element.
*/
void linkLast(E e) {
// 最后一个结点
final Node<E> l = last;
// 构造新结点,并设置新结点的前一个结点为旧的last结点
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
// 如果插入结点之前是空链表,则设置第一个结点和最后一个结点都指向新结点
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
4. 从尾取出数据
/**
* Unlinks non-null last node l.
*/
private E unlinkLast(Node<E> l) {
// l结点是最后一个结点,且不为空
// assert l == last && l != null;
final E element = l.item;
// 取前一个结点
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
// 让前一个结点作为新的last结点
last = prev;
// 链表为空,设置first为空
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
5.在指定是结点之前插入
/**
* Inserts element e before non-null Node succ.
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
// succ的前一个结点
final Node<E> pred = succ.prev;
// 设置三者之间的关系
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
// 如果succ是第一个结点
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
这个其实比较好理解