一、LinkedList 核心定义
LinkedList 是 Java 集合框架中 List 接口的双向链表实现 ,同时实现了 Deque 接口(支持双端队列操作),位于 java.util 包下;线程不安全,允许存储 null 值,元素按插入顺序排列。
二、底层原理
-
存储结构 :底层基于双向链表 实现,每个元素封装为
Node节点,节点包含 3 个属性:java
运行
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; } }链表有
first(头节点)和last(尾节点)指针,无需连续内存空间。 -
无扩容机制:链表节点按需创建,添加元素时仅需新建 Node 并调整指针指向,不存在数组扩容 / 拷贝的性能损耗。
三、核心特性
表格
| 特性 | 说明 |
|---|---|
| 随机访问 | 不支持高效随机访问(时间复杂度 O (n)),需从表头 / 表尾遍历到目标索引 |
| 增删效率 | 首尾增删:O (1)(仅调整指针);指定位置增删:O (n)(需先遍历找到节点) |
| 线程安全 | 线程不安全,多线程场景需使用 Collections.synchronizedList() 或 ConcurrentLinkedDeque |
| 允许 null 值 | 可存储 null 元素(支持多个 null) |
| 双端队列特性 | 实现 Deque 接口,可作为栈(push/pop)、队列(offer/poll)使用 |
| 内存占用 | 每个节点需存储前驱 / 后继指针,内存开销比 ArrayList 大 |
四、常用核心方法
java
运行
// 1. 初始化
LinkedList<String> list = new LinkedList<>();
LinkedList<String> list2 = new LinkedList<>(Arrays.asList("a", "b"));
// 2. 核心增删(双向链表特性)
list.add("java"); // 尾部添加(O(1))
list.addFirst("python"); // 头部添加(O(1))
list.addLast("c++"); // 尾部添加(等同于add(),O(1))
list.add(2, "go"); // 指定索引添加(O(n),需先遍历找节点)
list.removeFirst(); // 头部删除(O(1))
list.removeLast(); // 尾部删除(O(1))
list.remove(1); // 按索引删除(O(n))
list.remove("java"); // 按元素删除(O(n),需遍历)
// 3. 双端队列/栈操作(Deque接口)
list.offer("mysql"); // 队列:尾部添加
list.poll(); // 队列:头部移除(无元素返回null)
list.push("redis"); // 栈:头部添加
list.pop(); // 栈:头部移除(无元素抛异常)
// 4. 查
String first = list.getFirst(); // 头部元素(O(1))
String last = list.getLast(); // 尾部元素(O(1))
String elem = list.get(1); // 指定索引(O(n),效率低)
int index = list.indexOf("go"); // 查找元素索引(O(n))
// 5. 遍历(推荐方式)
// 方式1:增强for循环(底层迭代器,适合链表)
for (String s : list) {
System.out.println(s);
}
// 方式2:迭代器(支持遍历中删除)
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("go")) {
it.remove(); // 避免ConcurrentModificationException
}
}
// 方式3:降序遍历(双向链表优势)
ListIterator<String> listIt = list.listIterator(list.size());
while (listIt.hasPrevious()) {
System.out.println(listIt.previous());
}
五、面试高频考点
1. LinkedList vs ArrayList 核心对比(必背)
表格
| 维度 | ArrayList | LinkedList |
|---|---|---|
| 底层结构 | 动态数组(Object []) | 双向链表(Node 节点) |
| 随机访问 | O (1)(快,索引直接定位) | O (n)(慢,需遍历) |
| 增删效率 | 尾部 O (1),非尾部 O (n)(移动元素) | 首尾 O (1),中间 O (n)(找节点) |
| 内存占用 | 连续内存,有扩容冗余 | 非连续内存,节点含指针,开销大 |
| 适用场景 | 频繁查询、少量增删 | 频繁首尾增删、队列 / 栈场景 |
| 遍历效率 | for 循环(随机访问)更快 | 迭代器 / 增强 for 更快 |
2. 易错点
- 随机访问性能陷阱 :避免用
for (int i=0; i<size; i++) { list.get(i); }遍历 LinkedList,每次get(i)都会从头遍历,时间复杂度退化到 O (n²); - 栈 / 队列使用场景:LinkedList 实现了 Deque 接口,是 Java 中栈(替代过时的 Stack 类)、队列的常用实现;
- 遍历删除 :同 ArrayList,增强 for 循环中直接
list.remove()会抛ConcurrentModificationException,需用迭代器的remove(); - null 值处理 :允许存储多个 null,
indexOf(null)可正常查找 null 元素位置。
3. 扩展考点
- LinkedList 没有
ensureCapacity()/trimToSize()方法(无数组,无需扩容 / 缩容); - JDK 中 LinkedList 的节点是私有的静态内部类,外部无法直接访问;
- 双向链表支持正向 / 反向遍历(ListIterator 的
hasPrevious()/previous()),这是数组结构不具备的优势。
六、使用场景
- 适合频繁首尾增删的场景(如消息队列、任务栈);
- 适合需要双向遍历的场景;
- 不适合频繁随机查询的场景(优先用 ArrayList)。
总结
LinkedList 核心关键点:
- 底层是双向链表,无扩容机制,首尾增删 O (1)、随机访问 O (n),内存开销比 ArrayList 大;
- 实现 Deque 接口,可作为栈 / 队列使用,是这类场景的首选;
- 与 ArrayList 核心区别在底层结构,前者适合频繁增删,后者适合频繁查询。