LinkedList
LinkedList
是 Java 集合框架中的另一个常用类,它实现了 List
、Deque
和 Queue
接口,基于双向链表的结构,可以高效地进行插入和删除操作。
# 1. LinkedList 的基本特性
- 底层结构:
LinkedList
基于双向链表实现,每个节点包含对前后节点的引用。 - 高效的插入和删除:由于链表结构的特点,在链表头部和尾部的插入、删除操作时间复杂度为 O(1),在中间位置插入或删除操作需要遍历链表,时间复杂度为 O(n)。
- 随机访问性能差:由于链表的线性结构,随机访问一个元素的时间复杂度为 O(n)。
- 线程不安全:
LinkedList
不是线程安全的,如果需要在多线程环境中使用,需要进行额外的同步处理。
# 2. 构造函数
LinkedList
提供了两个主要的构造函数:
- 默认构造函数:创建一个空链表。
public LinkedList() { first = last = null; }
- 集合构造函数:根据传入的集合创建链表。
public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
# 3. 添加元素 (add()
方法)
LinkedList
的 add()
方法可以在链表的末尾添加元素,核心逻辑如下:
public boolean add(E e) {
linkLast(e);
return true;
}
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++;
}
- linkLast():创建一个新节点,将其链接到链表的尾部。
- 如果链表为空(
last == null
),新节点既是头节点也是尾节点。 - 否则,将新节点作为当前尾节点的后继节点。
- 如果链表为空(
LinkedList
的 addll()
方法用于批量添加元素,核心逻辑如下:
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
Node<E> pred, succ;
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
- addAll(int index, Collection<? extends E> c) 方法细节如下:
- checkPositionIndex(index):检查索引是否在有效范围内。
- toArray():将集合转换为数组,方便后续的批量插入操作。
- 插入逻辑:根据索引找到插入位置,将新元素逐一插入链表中。
- 调整前后节点的引用:对于插入位置的前驱和后继节点,调整其引用以保持链表的完整性。
- 更新链表大小和修改计数:增加链表的 size,并更新 modCount。
# 4. 获取元素 (get()
方法)
LinkedList
的 get()
方法用于通过索引获取元素,时间复杂度为 O(n):
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
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;
}
}
- node() 方法:根据索引在链表中找到对应的节点。
- 如果索引位于前半部分,从头节点开始遍历。
- 如果索引位于后半部分,从尾节点开始遍历。
# 5. 删除元素 (remove()
方法)
LinkedList
的 remove()
方法有两种重载方式,一种是通过索引删除元素,另一种是通过对象删除元素。
- 通过索引删除元素:
public E remove(int index) { checkElementIndex(index); return unlink(node(index)); } 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) { first = next; } else { prev.next = next; x.prev = null; } if (next == null) { last = prev; } else { next.prev = prev; x.next = null; } x.item = null; size--; modCount++; return element; }
- unlink() 方法:断开要删除节点与链表的连接,并调整前后节点的引用。
# 6. LinkedList 实现 Deque 的方法特性
LinkedList
还实现了 Deque
接口,使其可以用作双端队列,支持在两端插入和删除元素。以下是 LinkedList
实现 Deque
接口的一些方法及其源码分析:
# 6.1 添加元素到头部 (addFirst()
方法)
addFirst()
方法用于在链表头部添加元素:
public void addFirst(E e) {
linkFirst(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++;
}
- linkFirst():创建一个新节点,将其作为链表的头节点。
- 如果链表为空,新节点既是头节点也是尾节点。
- 否则,将新节点作为当前头节点的前驱节点。
# 6.2 添加元素到尾部 (addLast()
方法)
addLast()
方法用于在链表尾部添加元素,与 add()
方法相同:
public void addLast(E e) {
linkLast(e);
}
addLast()
方法直接调用linkLast()
方法,将元素添加到链表的尾部。
# 6.3 删除头部元素 (removeFirst()
方法)
removeFirst()
方法用于删除链表头部的元素:
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
private E unlinkFirst(Node<E> f) {
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // 帮助 GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
- unlinkFirst():断开头节点与链表的连接,并调整后继节点的引用。
- 如果链表只有一个节点,删除后头节点和尾节点都设置为
null
。
- 如果链表只有一个节点,删除后头节点和尾节点都设置为
# 6.4 删除尾部元素 (removeLast()
方法)
removeLast()
方法用于删除链表尾部的元素:
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
private E unlinkLast(Node<E> l) {
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // 帮助 GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
- unlinkLast():断开尾节点与链表的连接,并调整前驱节点的引用。
- 如果链表只有一个节点,删除后头节点和尾节点都设置为
null
。
- 如果链表只有一个节点,删除后头节点和尾节点都设置为
# 6.5 获取头部和尾部元素 (getFirst()
和 getLast()
方法)
getFirst()
方法:public E getFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return f.item; }
- 获取头节点的元素,如果链表为空则抛出
NoSuchElementException
。
- 获取头节点的元素,如果链表为空则抛出
getLast()
方法:public E getLast() { final Node<E> l = last; if (l == null) throw new NoSuchElementException(); return l.item; }
- 获取尾节点的元素,如果链表为空则抛出
NoSuchElementException
。
- 获取尾节点的元素,如果链表为空则抛出
# 7. 与 ArrayList 的比较
特性 | ArrayList | LinkedList |
---|---|---|
底层结构 | 动态数组 | 双向链表 |
随机访问性能 | O(1),查询性能优越 | O(n),需要遍历节点 |
插入和删除性能 | 尾部插入性能高;中间插入 O(n) | 头尾插入性能高;中间插入 O(n) |
内存占用 | 连续内存块,内存利用率高 | 每个节点有前后引用,内存占用较大 |
扩容机制 | 初始容量 10,每次扩容增至 1.5 倍 | 无需扩容,动态调整 |
线程安全 | 线程不安全 | 线程不安全 |
ArrayList
适用场景:适合频繁随机访问元素、插入和删除操作较少的场景,例如读取数据比较多、写入相对少的场景。LinkedList
适用场景:适合频繁插入和删除元素的场景,例如在头尾插入或删除元素的队列实现。
# 8. 总结
LinkedList
通过实现 Deque
接口,使其可以作为双端队列使用,支持在头尾两端进行高效的插入和删除操作。这些方法的实现利用了链表的双向引用特性,使得在头部和尾部的操作时间复杂度均为 O(1)。在需要频繁在两端进行操作的场景中,LinkedList
是非常合适的选择。
上次更新: 2024/10/31, 18:28:18