Java基础——集合进阶4
一、LinkedList集合
1.1 什么是 LinkedList?
LinkedList是 Java 集合框架中List接口的一个基于双向链表(Doubly Linked List)实现的可变长列表。
✅ 核心定义:
- 实现了
List、Deque(双端队列)、Cloneable、Serializable接口 - 底层使用双向链表存储元素
- 线程不安全
- 允许存储 null 值,且允许多个 null
💡 因为实现了 Deque,所以 LinkedList 也可以当作 栈(Stack)或队列(Queue) 使用!

1.2 LinkedList 的作用与适用场景
主要作用:
- 提供一个插入/删除高效、无需连续内存的有序容器
- 支持在任意位置高效增删(前提是已定位到节点)
- 可作为 双端队列(Deque):支持首尾插入/删除
✅ 适用场景(相对小众但关键):
- 需要频繁在列表开头或中间插入/删除元素
- 需要实现 队列(FIFO)或栈(LIFO) 功能(如任务调度、撤销操作)
- 元素数量动态变化大,且无法预估大小
- 对内存连续性无要求,且读取操作较少
⚠️ 注意:日常开发中,90% 场景仍推荐 ArrayList,LinkedList 仅用于特定性能需求!
1.3 常用方法与代码示例
1. 作为 List 使用(继承自 List 接口)
| 方法 | 说明 | 时间复杂度 |
|---|---|---|
add(E e) | 在末尾添加 | O(1) |
add(int index, E e) | 在指定位置插入 | O(n)(需遍历定位) |
get(int index) | 获取指定索引元素 | O(n) |
remove(int index) | 删除指定索引元素 | O(n)(需遍历) |
set(int index, E e) | 替换元素 | O(n) |
❗ 虽然链表“插入快”,但 通过索引操作仍需 O(n) 时间(因为要从头/尾遍历找位置)!
2. 作为 Deque(双端队列)使用(核心优势!)
| 方法 | 说明 | 时间复杂度 |
|---|---|---|
addFirst(E e) / offerFirst(E e) | 头部插入 | O(1) |
addLast(E e) / offerLast(E e) | 尾部插入 | O(1) |
removeFirst() / pollFirst() | 头部删除 | O(1) |
removeLast() / pollLast() | 尾部删除 | O(1) |
peekFirst() / peekLast() | 查看首/尾元素 | O(1) |
3.LinkedList独有的方法
但是这些方法在以后用的很少,只需要了解即可。

import java.util.*;public class LinkedListDemo {public static void main(String[] args) {// 1. 作为 List 使用List<String> list = new LinkedList<>();list.add("A");list.add(1, "B"); // 插入到索引1(需遍历,O(n))System.out.println(list.get(0)); // "A"(需遍历,O(n))// 2. 作为 Queue(队列)使用Queue<String> queue = new LinkedList<>();queue.offer("Task1");queue.offer("Task2");System.out.println(queue.poll()); // "Task1"(FIFO)// 3. 作为 Stack(栈)使用(推荐用 Deque)Deque<String> stack = new LinkedList<>();stack.push("Page1");stack.push("Page2");System.out.println(stack.pop()); // "Page2"(LIFO)// 4. 双端操作(Deque 特有)Deque<Integer> deque = new LinkedList<>();deque.addFirst(1);deque.addLast(2);System.out.println(deque.getFirst()); // 1System.out.println(deque.getLast()); // 2}
}
1.4 底层原理详解
1. 底层数据结构:双向链表(Node)
每个元素封装在一个 Node 节点中:

2. 关键成员变量
size:节点的个数;
Node<E> fisrt :头结点;
Node<E> last: 尾结点。

first和last分别指向链表的首尾,使得首尾操作均为 O(1)- 整个链表不要求内存连续,节点分散在堆中
3.关键源码解析(以 JDK 8 为例)
add()方法:调用linkLast();

✅ 作用:将元素 e 添加到链表末尾(即 last 后面),并返回 true。
linkLast:不断的创建节点并且把节点添加到链表的过程中。
void linkLast(E e) {final Node<E> l = last; // 记录当前最后一个节点final Node<E> newNode = new Node<>(l, e, null); // 创建新节点last = newNode; // 更新 last 指向新节点if (l == null) // 如果原链表为空first = newNode; // 首节点也指向它elsel.next = newNode; // 原最后一个节点的 next 指向新节点size++;modCount++;
}
✅ 第一步:new LinkedList<>(); —— 初始化
first = null ; last = null ; size = 0 ;
内存中没有任何节点,📌 状态:空链表
✅ 第二步:list.add("aaa");
①执行 linkLast("aaa"):
代码1:final Node<E> l = last; → l = null(因为链表为空)
代码2:final Node<E> newNode = new Node<>(l, "aaa", null); → 创建新节点:
prev = null(因为是第一个节点);item = "aaa";next = null ;
假设这个节点在内存中的地址是 0x0011。
代码3:last = newNode;→ last 现在指向 0x0011
代码4:if (l == null) → 成立
→ first = newNode;
→ first 也指向 0x0011
代码5:size++ → size = 1
代码6:modCount++ → 修改次数增加
✅ 结果:头尾指针指向null,表示这是第一个节点即头结点,值为"aaa"。
first → [null] → ["aaa"] → [null]↑last
✅ 第三步:list.add("bbb");
执行 linkLast("bbb"):
代码1:final Node<E> l = last;→ l = 0x0011(当前最后一个节点)
代码2:final Node<E> newNode = new Node<>(l, "bbb", null);→ 创建新节点:
prev = 0x0011(前一个节点); item = "bbb" ; next = null ;
假设地址为 0x0022。
代码3:last = newNode;→ last 指向 0x0022;
代码4:if (l == null) → 不成立
→ l.next = newNode;
→ 将 0x0011 的 next 指针指向 0x0022
代码5:size++ → size = 2 节点长度+1
✅ 结果:节点2的prev指针指向0x0011,值为"bbb",next指向null 表示尾指针
0x0011的next指向0x0022。
---------------
first → [null] → ["aaa"] → ["bbb"] → [null]↑ ↑0x0011 0x0022↑last

🧱 四、关键点总结(结合图示)
| 步骤 | 操作 | 内存变化 |
|---|---|---|
| 1. 初始化 | new LinkedList() | first=null, last=null |
| 2. 添加第一个元素 | add("aaa") | 创建节点 0x0011,first 和 last 都指向它 |
| 3. 添加第二个元素 | add("bbb") | 创建 0x0022,prev=0x0011,0x0011.next=0x0022,last=0x0022 |
| 4. 添加第三个元素 | add("ccc") | 创建 0x0033,prev=0x0022,0x0022.next=0x0033,last=0x0033 |
1.5、LinkedList vs ArrayList 详细对比
| 特性 | LinkedList | ArrayList |
|---|---|---|
| 底层结构 | 双向链表 | 动态数组 |
| 随机访问(get(i)) | ❌ O(n) | ✅ O(1) |
| 尾部 add/remove | ✅ O(1) | ✅ O(1)(均摊) |
| 头部 add/remove | ✅ O(1) | ❌ O(n)(需移动所有元素) |
| 中间插入/删除 | ✅ O(1)(定位后) ❌ O(n)(含定位时间) | ❌ O(n) |
| 内存占用 | 较大(每个节点存 prev/next + 对象头) | 较小(仅数组) |
| CPU 缓存友好性 | ❌ 差(内存分散) | ✅ 好(连续内存) |
| 是否实现 Deque | ✅ 是 | ❌ 否 |
| 默认初始容量 | 无概念 | 0(首次 add 扩到 10) |
| 扩容机制 | 无需扩容 | 1.5 倍扩容 |
💡 关键结论:
- 如果你频繁通过索引访问 → 选
ArrayList - 如果你频繁在首尾操作(如队列/栈)→ 选
LinkedList - 如果你频繁在中间插入/删除 → 两者都慢!(因定位需 O(n)),此时应考虑是否设计合理
性能测试对比(10 万次操作):
| 操作 | ArrayList | LinkedList |
|---|---|---|
| 尾部 add | ✅ 极快 | ✅ 极快 |
| 头部 add | ❌ 极慢(移动全部) | ✅ 极快 |
| get(50000) | ✅ 瞬间 | ❌ 需遍历一半 |
| 遍历(for-each) | ✅ 快(缓存友好) | ❌ 慢(指针跳转) |
📌 实测结论:除非明确需要首尾高效操作,否则
ArrayList综合性能更优!
1.6 高频面试题(面经精要)
Q1:LinkedList 的底层结构是什么?
答:基于双向链表,每个节点包含数据、前驱指针(prev)和后继指针(next),并维护
first和last指向首尾节点。
Q2:LinkedList 查询为什么慢?
答:因为链表不支持随机访问,必须从头或尾开始遍历才能定位到指定索引,时间复杂度为 O(n)。
Q3:LinkedList 和 ArrayList 如何选择?
答:
- 读多写少、需随机访问 →
ArrayList- 频繁在首尾增删(如队列/栈) →
LinkedList- 一般场景默认选
ArrayList,因其缓存友好、综合性能更好
Q4:LinkedList 可以当栈或队列用吗?
答:可以!因为它实现了
Deque接口,支持:
- 栈:
push(e)/pop()/peek()- 队列:
offer(e)/poll()/peek()
⚠️ 但注意:Java 官方文档建议优先使用
ArrayDeque实现栈/队列,性能优于LinkedList!
Q5:为什么 LinkedList 没有像 ArrayList 那样的初始容量?
答:因为链表是动态链接节点,不需要预先分配连续内存空间,每次 add 只需创建一个新节点,因此没有“容量”概念。
Q6:LinkedList 的内存开销比 ArrayList 大多少?
答:每个元素额外占用:
- 两个引用(prev + next):64 位 JVM 上约 16 字节
- 对象头(Node 对象本身):约 12~16 字节
- 总计每个元素多消耗 ~32 字节,远高于 ArrayList 的纯数组存储。
声明:
分析借鉴于通义AI
以上均来源于B站@ITheima的教学内容!!!
本人跟着视频内容学习,整理知识引用
