LinkedList底层实现就是(双向)链表,包括链表节点的插入、删除、修改等一系列操作。与数组实现的ArrayList相比,在链表中查找的效率要低很多,但是向表中插入、删除等操作效率就要高得多了。LinkedList在实现了List接口的同时,又实现了Queue、Deque接口,也就是说LinkedList还能够实现(双端)队列(FIFO)、栈(LIFO)的功能。
底层数据结构:
transient int size = 0; // 链表当前存储元素个数
transient Node<E> first; // 链表头节点
transient Node<E> last; // 链表尾节点
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;
}
}
构造方法:
public LinkedList() { // 构建一个空链表
}
public LinkedList(Collection<? extends E> c) { // 根据集合c中的元素构建初始链表
this();
addAll(c); // 将集合c转换成数组,然后循环数组元素一个一个插入到链表中
}
node(index):
LinkedList能够通过下标找到某个节点,这里的下标其实是某个节点与头结点的相对位置,0就是头结点、1就是头结点的后直接在后继节点,size-1就是尾节点。在指定位置插入、删除和修改等操作都要使用根据位置查找节点的node()方法。
/**
* 返回指定位置的节点
* 调用本方法前,都应该检测index值得合法性
*/
Node<E> node(int 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;
}
}
link*方法:
private void linkFirst(E e) { // 插入头结点
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode; // 修改头结点
if (f == null) // 如果插入前链表头结点为空(空链表),则尾节点也指向这个新节点
last = newNode;
else // 否则修改之前的头结点的前驱为新的头结点
f.prev = newNode;
size++;
modCount++;
}
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++;
}
void linkBefore(E e, Node<E> succ) { // 在指定节点succ之前插入节点
final Node<E> pred = succ.prev; // 保存指定节点的前驱节点pred
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode; // 修改succ节点的前驱为插入节点
if (pred == null) // pred为null,说明succ是头结点,插入节点为新的头结点
first = newNode;
else // 否则,修改pred后继结点为新插入的节点
pred.next = newNode;
size++;
modCount++;
}
unlink方法*:
private E unlinkFirst(Node<E> f) { // 删除头结点,返回删除节点中保存的元素
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null;
first = next; // 修改头结点
if (next == null) // 如果新的头结点为null,则修改后链表为空,令尾节点也也为null(之前与头结点相同)
last = null;
else // 修改新的头结点前驱为null
next.prev = null;
size--;
modCount++;
return element;
}
private E unlinkLast(Node<E> l) { // 删除尾结点,返回删除节点中保存的元素
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null;
last = prev; // 修改尾节点
if (prev == null) // 如果新的尾结点为null,则修改后链表为空,令头节点也为null(之前与尾节结点相同)
first = null;
else // 修改新的尾结点后继为null
prev.next = null;
size--;
modCount++;
return element;
}
E unlink(Node<E> x) { // 删除指定节点,并返回删除节点中保存的元素
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) { // 如果删除的是头结点,则头结点指向x后继节点
first = next;
} else { // 将节点x的前驱结点的后继节点指向x的后继结点
prev.next = next;
x.prev = null;
}
if (next == null) { // 如果删除的是尾节点,则尾节点指向x前驱节点
last = prev;
} else { // 将节点x的后继结点的前驱节点指向x的前驱节点
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
LinkedList栈操作:
LInkedList实现了Deque接口,其中包括push、和pop两个方法。push操作是向链表插入新的头结点,pop操作是删除头结点并返回被删除节点中保存的元素。只对链表进行push和pop操作就能实现LIFO(后进先出)。
public void push(E e) { // 压栈操作
addFirst(e); // addFirst中调用linkFirst
}
public E pop() { // 出栈操作,如果当前栈空,则报NoSuchElementException异常
return removeFirst(); // removeFirst中调用unlinkFirst
}
LinkedList(双端)队列操作:
Deque继承了Queue接口,在队列的基础上,实现了双端队列,于是LinkedList也可以当作双端队列来使用。队列操作包含一些列操作方法,这些队列方法实现了可以从任意一端进行插入和删除,也包括只返回不删除操作。根据需要选择不同的方法组合来达到编程目的。
// 第一类:特点:如果队头节点为null(队列空),报NoSuchElementException异常
public E element() {
return getFirst();
}
public E remove() {
return removeFirst();
}
// 第二类:peek,特点:只返队头(尾)元素不删除
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
// 第三类:poll,特点:返回队头(尾)元素并删除
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
// 第四类:offer,特点:向队列头(尾)插入节点
public boolean offer(E e) {
return add(e);
}
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
public boolean offerLast(E e) {
addLast(e);
return true;
}
import java.util.Deque;
import java.util.LinkedList;
public class LinkedListDemo {
public static void main(String[] args) {
Deque<Integer> deque = new LinkedList<>();
deque.offer(1);
deque.offer(2);
deque.offer(3);
// deque: head{1, 2, 3}tail
System.out.println(deque.poll()); // 1
System.out.println(deque.poll()); // 2
System.out.println(deque.poll()); // 3
System.out.println(deque.poll()); // null
Deque<Integer> stack = new LinkedList<>();
stack.push(1);
stack.push(2);
stack.push(3);
// stack: top{3, 2, 1}bottom
System.out.println(stack.pop()); // 3
System.out.println(stack.pop()); // 2
System.out.println(stack.pop()); // 1
}
}