相关博客
- Java集合框架介绍
- Java集合框架 ——— ArrayList 基础使用
- Java集合框架 ——— ArrayList 源码分析
- Java集合框架 ——— HashMap 基础使用
- Java集合框架 ——— HashMap 源码分析(一)
- Java集合框架 ——— HashMap 源码分析(二)
- Java集合框架 ——— LinkedList 基础使用
简介
LinkedList
是一个基于双向链表实现的集合类,经常被拿来和 ArrayList
做比较。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
LinkedList 继承自AbstractSequentialList
,它决定了 LinkedList 只能支持按次序访问。此外 LinkedList 还实现了 Deque
接口,它具有双端队列的特性,支持从两端插入和删除元素,方便实现栈和队列等数据结构。因此 LinkedList 实现了很多队列的方法,在开发中也可以当做一个堆栈来使用。
Node 节点
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;
}
}
主要属性
// 集合长度
transient int size = 0;
/**
* 指向第一个节点的指针。
*/
transient Node<E> first;
/**
* 指向最后一个节点的指针。
*/
transient Node<E> last;
构造方法
/**
* 无参构造器
*/
public LinkedList() {
}
/**
* 按照集合的迭代器返回的顺序构造包含指定集合的元素的列表。
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
插入元素
add()
add()
方法有两个版本:
add(E e)
:在集合尾部插入元素,时间复杂度为 O(1)。add(int index, E element)
: 在指定位置插入元素。由于链表的特性,指定位置插入时需要移动指针,所以时间复杂度为 O(n)。
/**
* 将指定的元素追加到此列表的末尾。
*/
public boolean add(E e) {
// 调用方法插入到末尾
linkLast(e);
return true;
}
/**
* 在此列表中的指定位置插入指定的元素。将当前位于该位置的元素(如果有)和任何后续元素向右移动(将一个元素添加到其索引中)。
*
* @param index 要插入指定元素的索引
* @param element 要插入的元素
*/
public void add(int index, E element) {
// 检查 index 是否合法,linkedList
checkPositionIndex(index);
// 如果index 等于 size,相当于添加在末尾,直接调用 linkLast 方法
if (index == size)
linkLast(element);
else
// 如果在集合中间,调用linkBefore,插入元素并且移动后续元素
linkBefore(element, node(index));
}
相关方法
node方法在下文查找元素中介绍,checkPositionIndex 方法比较简单,判断一下 index 在不在 0 到 size 之间就好了,可以自己看一下。
/**
* 链接 e 作为最后一个元素。
*/
void linkLast(E e) {
// 临时保存原集合最后一个元素
final Node<E> l = last;
// 即将加入到列表的元素节点
final Node<E> newNode = new Node<>(l, e, null);
// 重新设置最后一个节点
last = newNode;
if (l == null)
// 如果 原集合 最后一个元素是 null,表示集合为空,则把第一个元素也赋值成 newNode
first = newNode;
else
// 指向新的后继节点
l.next = newNode;
size++;
modCount++;
}
/**
* 在非 null 节点 succ 之前插入元素 e
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
// 获得要插入位置的节点的前驱节点
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
// succ重新连接前驱节点
succ.prev = newNode;
if (pred == null)
// 如果 succ 是头结点,重设头结点
first = newNode;
else
// 指向新的后继节点
pred.next = newNode;
size++;
modCount++;
}
addFirst() & addLast()
/**
* 在此列表的开头插入指定的元素。
*/
public void addFirst(E e) {
linkFirst(e);
}
/**
* 将指定的元素追加到此列表的末尾。
*/
public void addLast(E e) {
linkLast(e);
}
相关方法
linkFirst
方法和 linkLast
方法实现思路类似,可以参考上文自行理解
/**
* 链接 e 作为第一个元素。
*/
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++;
}
其他插入元素方法
其余的方法只是简单的调用一下其他已经实现的方法,不在过多介绍
/**
* Adds the specified element as the tail (last element) of this list.
*
* @param e the element to add
* @return {@code true} (as specified by {@link Queue#offer})
* @since 1.5
*/
public boolean offer(E e) {
return add(e);
}
// Deque operations
/**
* Inserts the specified element at the front of this list.
*
* @param e the element to insert
* @return {@code true} (as specified by {@link Deque#offerFirst})
* @since 1.6
*/
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
/**
* Inserts the specified element at the end of this list.
*
* @param e the element to insert
* @return {@code true} (as specified by {@link Deque#offerLast})
* @since 1.6
*/
public boolean offerLast(E e) {
addLast(e);
return true;
}
查找元素
get()
/**
* 返回此列表中指定位置的元素。
*
* @param index 要返回的元素的索引
*/
public E get(int index) {
// 查看 index 是否合法
checkElementIndex(index);
return node(index).item;
}
相关方法
/**
* 返回指定元素索引处的(非 null) 节点。
*/
Node<E> node(int index) {
// 判断一下元素索引下标是否合法
// assert isElementIndex(index);
// 判断下标位置,选择正向遍历还是反向遍历
if (index < (size >> 1)) {
Node<E> x = first;
// 下标在集合前半段,从前向后遍历找到下标 index,返回节点
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
// 下标在集合后半段,从后向前遍历找到下标 index,返回节点
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
getFirst() & getLast()
/**
* 返回此列表中的第一个元素。
*/
public E getFirst() {
// 通过 first 属性得到头结点
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
/**
* 返回此列表中的最后一个元素。
*/
public E getLast() {
// 通过 last 属性得到尾结点
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
其他方法
其余的三个检索方法也只是通过 first 和 last 属性返回节点
/**
* Retrieves, but does not remove, the head (first element) of this list.
*
* @return the head of this list, or {@code null} if this list is empty
* @since 1.5
*/
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
/**
* Retrieves, but does not remove, the first element of this list,
* or returns {@code null} if this list is empty.
*
* @return the first element of this list, or {@code null}
* if this list is empty
* @since 1.6
*/
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
/**
* Retrieves, but does not remove, the last element of this list,
* or returns {@code null} if this list is empty.
*
* @return the last element of this list, or {@code null}
* if this list is empty
* @since 1.6
*/
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
删除元素
list 的删除方法
/**
* 从此列表中删除指定元素的第一个匹配项(如果存在)。
*
* @param o 要从此列表中删除的元素
*/
public boolean remove(Object o) {
if (o == null) {
// 遍历
for (Node<E> x = first; x != null; x = x.next) {
// 第一个符合条件的item
if (x.item == null) {
// 移除
unlink(x);
return true;
}
}
} else {
// 和条件为 true 的逻辑相同
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
/**
* 从此列表中删除所有元素。此调用返回后,列表将为空。
*/
public void clear() {
// 遍历,把节点全部变为null,方便GC回收
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}
相关方法
/**
* 取消链接非空节点 x。
*/
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 指针置为 null,方便 GC 回收
x.prev = null;
}
if (next == null) {
// 要删除的是尾结点,重设尾结点
last = prev;
} else {
next.prev = prev;
x.next = null;
}
// 将当前节点元素置为 null,方便 GC 回收
x.item = null;
size--;
modCount++;
return element;
}
Deque 的删除方法
通过 Deque 接口实现的删除方法,主要是通过 LinkedList 内部私有方法unlinkFirst
和unlinkLast
实现,可以参考 unlink
方法和clear
方法。
/**
* Removes and returns the first element from this list.
*
* @return the first element from this list
* @throws NoSuchElementException if this list is empty
*/
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
/**
* Removes and returns the last element from this list.
*
* @return the last element from this list
* @throws NoSuchElementException if this list is empty
*/
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
相关方法
/**
* 取消链接非 null 的第一个节点 f。
*/
private E unlinkFirst(Node<E> 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;
}
/**
* 取消链接非空的最后一个节点 l。
*/
private E unlinkLast(Node<E> 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 = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}