Java LinkedList
在 Java 中,
三、
LinkedList是集合框架中List接口的重要实现类,同时还实现了Deque(双端队列)和Queue接口,底层基于双向链表结构实现。它的特性与ArrayList(基于动态数组)形成鲜明对比,适用于频繁插入、删除的场景。本文将从底层结构、核心方法、特性对比、适用场景等方面详细解析LinkedList。
一、底层结构:双向链表
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;
}
}
- 头节点:链表的第一个节点,
prev为null; - 尾节点:链表的最后一个节点,
next为null; - 双向性:每个节点可通过
prev和next访问前序和后续节点,便于双向遍历。
这种结构决定了
LinkedList的核心特性:- 插入 / 删除效率高:只需修改节点的
prev和next指针(已知位置时复杂度为O(1)); - 查询效率低:访问指定索引的元素时,需从表头或表尾开始遍历(复杂度为
O(n)); - 内存开销大:每个节点除存储数据外,还需额外存储两个指针。
二、核心 API 与常用方法
LinkedList提供了丰富的方法,涵盖List接口的基础操作,以及Deque接口的队列 / 栈操作,以下是常用方法分类解析:1. 构造方法
LinkedList():创建一个空的LinkedList;LinkedList(Collection<? extends E> c):通过已有集合初始化LinkedList(将集合元素按顺序添加到链表)。
// 空链表
LinkedList<String> list = new LinkedList<>();
// 用集合初始化
List<String> initList = Arrays.asList("a", "b", "c");
LinkedList<String> list2 = new LinkedList<>(initList); // 包含 ["a", "b", "c"]
2. 基础增删改查(List 接口)
| 方法 | 功能描述 | 时间复杂度 |
|---|---|---|
boolean add(E e) |
在链表尾部添加元素 | O(1) |
void add(int index, E e) |
在指定索引位置插入元素 | O(n)(需先遍历到索引位置) |
void addFirst(E e) |
在链表头部添加元素(等价于push(e)) |
O(1) |
void addLast(E e) |
在链表尾部添加元素(等价于add(e)) |
O(1) |
E remove() |
删除并返回链表头部元素(等价于removeFirst()) |
O(1) |
E remove(int index) |
删除并返回指定索引的元素 | O(n) |
boolean remove(Object o) |
删除第一个匹配的元素(需 equals 判断) | O(n) |
E removeFirst() |
删除并返回头部元素 | O(1) |
E removeLast() |
删除并返回尾部元素 | O(1) |
E get(int index) |
获取指定索引的元素 | O(n) |
E getFirst() |
获取头部元素 | O(1) |
E getLast() |
获取尾部元素 | O(1) |
E set(int index, E e) |
替换指定索引的元素并返回旧值 | O(n) |
int size() |
返回元素个数 | O(1) |
boolean isEmpty() |
判断链表是否为空 | O(1) |
3. 队列 / 栈操作(Deque 接口)
由于
LinkedList实现了Deque,可直接作为队列(FIFO) 或栈(LIFO) 使用:| 场景 | 方法(功能) | 说明 |
|---|---|---|
| 队列 | boolean offer(E e):尾部添加元素 |
成功返回true(队列满时返回false,但LinkedList无容量限制,等价于add(e)) |
E poll():删除并返回头部元素 |
队列为空时返回null(区别于remove()的抛异常) |
|
E peek():获取头部元素(不删除) |
队列为空时返回null(区别于getFirst()的抛异常) |
|
| 栈 | void push(E e):头部添加元素 |
等价于addFirst(e) |
E pop():删除并返回头部元素 |
等价于removeFirst()(栈为空时抛NoSuchElementException) |
4. 遍历方法
LinkedList支持多种遍历方式,效率因方式而异:-
迭代器遍历(推荐,支持边遍历边删除):
LinkedList<String> list = new LinkedList<>(Arrays.asList("a", "b", "c")); // 正向遍历 Iterator<String> it = list.iterator(); while (it.hasNext()) { System.out.println(it.next()); // a, b, c } // 双向遍历(ListIterator支持向前/向后移动) ListIterator<String> lit = list.listIterator(list.size()); // 从尾部开始 while (lit.hasPrevious()) { System.out.println(lit.previous()); // c, b, a } -
增强 for 循环(底层还是迭代器):
for (String s : list) { System.out.println(s); } -
索引遍历(不推荐,效率低,每次
get(i)都需从头遍历):for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); // 时间复杂度O(n²) }
三、LinkedList vs ArrayList:核心区别
| 特性 | LinkedList | ArrayList |
|---|---|---|
| 底层结构 | 双向链表 | 动态数组(容量不足时扩容为 1.5 倍) |
| 内存开销 | 高(每个节点需存储前后指针) | 低(仅存储数据,可能有冗余容量) |
随机访问(get(i)) |
慢(O(n)),需遍历 |
快(O(1)),直接通过索引访问 |
| 插入 / 删除(中间) | 快(O(1),已知前后节点时) |
慢(O(n),需移动后续元素) |
| 插入 / 删除(头尾) | 快(O(1)) |
尾部快(O(1)),头部慢(O(n)) |
| 线程安全性 | 不安全(多线程操作需同步) | 不安全(同左) |
| 适用场景 | 频繁插入 / 删除、实现队列 / 栈 | 频繁查询、随机访问 |
四、注意事项
-
线程不安全:
LinkedList未实现同步,多线程环境下同时修改会导致数据不一致。解决方式:- 用
Collections.synchronizedList()包装:List<String> syncList = Collections.synchronizedList(new LinkedList<>()); - 改用线程安全的
ConcurrentLinkedDeque(JUC 包)。
- 用
-
遍历中删除元素:
- 用迭代器的
remove()方法(安全):Iterator<String> it = list.iterator(); while (it.hasNext()) { if (it.next().equals("a")) { it.remove(); // 正确:迭代器内部维护指针,避免ConcurrentModificationException } } - 不可用
for循环直接remove(i)(会导致索引偏移,漏删或越界)。
- 用迭代器的
-
避免频繁
get(i):因get(i)需从头 / 尾遍历,频繁调用会导致效率极低(如遍历链表用get(i)时间复杂度为O(n²))。
五、适用场景
- 频繁插入 / 删除:尤其是在链表中间位置(如实现链表式数据结构、日志记录动态增删);
- 队列 / 栈实现:利用
Deque接口的方法,简洁实现 FIFO 队列或 LIFO 栈; - 双向遍历需求:需向前 / 向后遍历元素(如浏览器历史记录的前进 / 后退)。
总结
LinkedList是基于双向链表的集合类,以牺牲查询效率为代价,换取了高效的插入 / 删除操作和灵活的双向操作能力。在选择LinkedList还是ArrayList时,核心依据是操作类型:- 若以查询、随机访问为主,选
ArrayList; - 若以插入、删除(尤其是中间位置) 或队列 / 栈操作为主,选
LinkedList。
掌握其底层结构和方法特性,能帮助开发者在实际场景中做出最优选择,提升程序性能。
浙公网安备 33010602011771号