目录
[2.1 基于List的操作](#2.1 基于List的操作)
[2.2 队列(Queue)、栈(Stack)和双端队列(Deque)操作](#2.2 队列(Queue)、栈(Stack)和双端队列(Deque)操作)
[2.3 遍历](#2.3 遍历)
一、概念
LinkedList
在 Java 中是一个基于双向链表 实现的 List
接口的实现类。它同时实现了 List
和 Deque
(双端队列)接口,所以它同时具备了列表和队列的特性。
核心特点:
-
非连续存储:元素分散存储在堆内存中,通过引用相连。这使其在内存利用上更灵活,但每个元素需要额外的空间来存储前后节点的引用(内存开销更大)。
-
动态大小:与数组不同,链表的大小是动态的,可以自由地添加和删除元素,无需担心容量问题。
-
插入和删除高效 :在已知位置(通过迭代器定位)进行插入和删除操作的时间复杂度是 O(1) 。这是它相对于
ArrayList
最大的优势。因为它只需要修改相邻节点的引用即可,而不需要像数组那样移动大量元素。 -
随机访问低效 :根据索引访问元素(
get(i)
,set(i, element)
)的时间复杂度是 O(n)。因为它必须从链表的头部(或尾部,JDK 做了优化)开始遍历,直到找到第 i 个节点。 -
实现了双端队列(Deque) :提供了丰富的方法,如
addFirst()
,addLast()
,removeFirst()
,removeLast()
,getFirst()
,getLast()
等,可以非常方便地将其用作栈(Stack)、队列(Queue)或双端队列(Deque)。
二、常用操作
2.1 基于List的操作
java
LinkedList<String> list = new LinkedList<>();
// 添加元素
list.add("Apple"); // 添加到尾部
list.add(1, "Banana"); // 在指定索引位置插入
list.addFirst("First"); // 添加到头部 (Deque 方法)
list.addLast("Last"); // 添加到尾部 (Deque 方法,等同于 add())
// 获取元素
String first = list.get(0); // 根据索引获取
String head = list.getFirst(); // 获取头部元素
String tail = list.getLast(); // 获取尾部元素
// 删除元素
list.remove(1); // 根据索引删除
list.remove("Apple"); // 根据元素内容删除(首次出现)
list.removeFirst(); // 删除并返回头部元素
list.removeLast(); // 删除并返回尾部元素
// 检查大小和内容
int size = list.size();
boolean isEmpty = list.isEmpty();
boolean hasApple = list.contains("Apple");
// 替换元素
list.set(0, "New First"); // 将索引0位置的元素替换
2.2 队列(Queue)、栈(Stack)和双端队列(Deque)操作
java
LinkedList<String> queue = new LinkedList<>();
// 作为普通队列 (FIFO: First-In-First-Out)
queue.offer("A"); // 入队(添加到尾部),推荐使用
queue.add("B"); // 入队(添加到尾部),失败会抛异常
String s1 = queue.poll(); // 出队(移除并返回头部元素),队列空则返回null
String s2 = queue.remove(); // 出队,队列空则抛异常 NoSuchElementException
String s3 = queue.peek(); // 获取但不移除头部元素,空则返回null
String s4 = queue.element(); // 获取但不移除头部元素,空则抛异常
// 作为栈 (LIFO: Last-In-First-Out)
LinkedList<String> stack = new LinkedList<>();
stack.push("A"); // 压栈(添加到头部)
stack.push("B");
String top = stack.pop(); // 出栈(移除并返回头部元素)
String top2 = stack.peek(); // 查看栈顶元素
// 作为双端队列 (Deque)
queue.offerFirst("A"); // 添加到头部
queue.offerLast("Z"); // 添加到尾部(等同于 offer)
String first = queue.pollFirst(); // 从头部移除
String last = queue.pollLast(); // 从尾部移除
典型使用场景:
-
实现栈(LIFO) :
addFirst()
/removeFirst()
-
实现队列(FIFO) :
offer()
/poll()
或addLast()
/removeFirst()
-
实现双端队列 :各种
First
/Last
方法
操作 | 方法示例 | 时间复杂度 | 说明 |
---|---|---|---|
头部插入 | addFirst(e) , offerFirst(e) |
O(1) | |
尾部插入 | add(e) , addLast(e) , offer(e) , offerLast(e) |
O(1) | |
指定位置插入 | add(index, e) |
O(n) | 主要耗时在遍历到 index 位置 |
头部删除 | removeFirst() , pollFirst() |
O(1) | |
尾部删除 | removeLast() , pollLast() |
O(1) | |
指定元素/位置删除 | remove(o) , remove(index) |
O(n) | 主要耗时在遍历找到元素或位置 |
随机访问(获取) | get(index) |
O(n) | 需要遍历 |
随机访问(修改) | set(index, e) |
O(n) | 需要遍历到位置后再修改,修改本身是 O(1) |
查找元素位置 | indexOf(o) |
O(n) | 需要遍历比较 |
检查是否包含 | contains(o) |
O(n) | 内部调用 indexOf |
队列使用示例:
java
import java.util.LinkedList;
import java.util.Queue;
public class QueueExample {
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
// 生产者:入队10个任务
for (int i = 0; i < 10; i++) {
queue.offer(i);
System.out.println("任务入队: " + i);
}
// 消费者:处理所有任务
while (!queue.isEmpty()) {
Integer task = queue.poll();
System.out.println("处理任务: " + task);
}
}
}
2.3 遍历
千万不要用 for-i
循环(基于索引的循环)来遍历 LinkedList
!
因为每次 get(i)
都会从链表头开始遍历,会导致时间复杂度变为 O(n²),性能极差。
java
// foreach 循环(推荐):语法简洁,底层使用迭代器,效率高。
for (String item : list) {
System.out.println(item);
}
// 迭代器(Iterator):更灵活,可以在遍历时使用 iterator.remove() 安全地删除元素。
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if ("某些条件") {
iterator.remove(); // 安全删除!
}
}
// ListIterator:功能更强大的迭代器,可以双向遍历(向前/向后)以及在遍历时添加和修改元素。
ListIterator<String> listIterator = list.listIterator();
while (listIterator.hasNext()) {
String next = listIterator.next();
listIterator.set("Modified: " + next); // 修改元素
listIterator.add("Added"); // 添加元素
}