Java集合框架:LinkedList
目录
文档链接
一、LinkedList 核心原理
1.1 底层数据结构
1.2 接口实现
1.3 重要特性
二、构造方法详解
2.1 无参构造方法
2.2 集合参数构造方法
三、核心方法详解与示例
3.1 元素添加方法
3.1.1 基本添加操作
3.1.2 首尾添加操作(Deque 特性)
3.1.3 栈操作
3.2 元素删除方法
3.2.1 基本删除操作
3.2.2 首尾删除操作(Deque 特性)
3.2.3 栈操作
3.3 元素查询方法
3.3.1 基本查询操作
3.3.2 队列风格查询(Deque 特性)
3.4 元素修改方法
3.5 其他常用方法
四、LinkedList 与 ArrayList 对比分析
五、线程安全问题
六、迭代器与快速失败机制
七、常见问题与注意事项
7.1 关于 null 元素
7.2 序列化问题
7.3 克隆问题
在 Java 集合框架中,LinkedList 是一个非常重要且常用的实现类。它以双向链表为底层数据结构,同时实现了 List 和 Deque 接口,兼具列表和双端队列的特性。
文档链接
点击这里,LinkedList
一、LinkedList 核心原理
1.1 底层数据结构
LinkedList 的底层实现是双向链表,每个节点(Node)包含三个部分:
- 存储元素的值(item)
- 指向前驱节点的引用(prev)
- 指向后继节点的引用(next)
这种结构使得 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;}
}
Node
是私有的静态内部类,只能在 LinkedList 内部访问- 每个节点包含三个部分:元素本身、前驱节点引用、后继节点引用
- 这种结构允许从任意节点向两个方向遍历链表
LinkedList 通过以下几个字段维护整个链表结构:
transient Node<E> first; // 指向头节点
transient Node<E> last; // 指向尾节点
transient int size = 0; // 元素数量
protected transient int modCount = 0; // 从AbstractList继承,用于快速失败机制
first
和last
指针分别指向链表的头和尾,使得首尾操作可以在 O (1) 时间内完成size
记录元素数量,避免每次需要大小时遍历整个链表modCount
用于记录结构修改次数,迭代器通过检查此值实现快速失败
1.2 接口实现
LinkedList 实现了以下关键接口:
List<E>
:提供列表的基本操作(增删改查)Deque<E>
:提供双端队列的操作(首尾元素操作)Cloneable
:支持克隆Serializable
:支持序列化
这意味着 LinkedList 既可以作为普通列表使用,也可以作为队列、栈等数据结构使用。
1.3 重要特性
- 允许 null 元素:可以添加 null 值到集合中
- 非线程安全: 多线程环境下需要外部同步
- 快速失败机制: 迭代器在检测到并发修改时会抛出
ConcurrentModificationException
- 无容量限制: 链表可以动态增长,理论上只受内存限制
二、构造方法详解
LinkedList 提供了两个构造方法:
2.1 无参构造方法
创建一个空的 LinkedList:
// 构造空链表
LinkedList<String> list = new LinkedList<>();
System.out.println(list); // 输出:[]
2.2 集合参数构造方法
创建包含指定集合元素的 LinkedList,元素顺序与集合迭代器顺序一致:
// 从其他集合初始化
List<String> initial = Arrays.asList("a", "b", "c");
LinkedList<String> list = new LinkedList<>(initial);
System.out.println(list); // 输出:[a, b, c]
三、核心方法详解与示例
3.1 元素添加方法
3.1.1 基本添加操作
add(E e)
: 在链表尾部添加元素add(int index, E element)
: 在指定位置插入元素addAll(Collection<? extends E> c)
: 在尾部添加集合所有元素addAll(int index, Collection<? extends E> c)
:在指定位置插入集合所有元素
LinkedList<String> list = new LinkedList<>();// 在尾部添加元素
list.add("A");
list.add("B");
System.out.println(list); // [A, B]// 在指定位置插入
list.add(1, "C");
System.out.println(list); // [A, C, B]// 添加整个集合
List<String> newElements = Arrays.asList("D", "E");
list.addAll(newElements);
System.out.println(list); // [A, C, B, D, E]// 在指定位置添加集合
list.addAll(2, Arrays.asList("F", "G"));
System.out.println(list); // [A, C, F, G, B, D, E]
3.1.2 首尾添加操作(Deque 特性)
addFirst(E e)
: 在链表头部添加元素addLast(E e)
: 在链表尾部添加元素(与 add 方法功能相同)offer(E e)
: 在尾部添加元素(与 add 类似,队列风格)offerFirst(E e)
: 在头部添加元素(与 addFirst 类似)offerLast(E e)
: 在尾部添加元素(与 addLast 类似)
LinkedList<String> list = new LinkedList<>();list.addFirst("A"); // 头部添加
list.addLast("B"); // 尾部添加
System.out.println(list); // [A, B]list.offer("C"); // 尾部添加
list.offerFirst("D");// 头部添加
System.out.println(list); // [D, A, B, C]
3.1.3 栈操作
push(E e)
:将元素推入栈(实际上是在头部添加,与 addFirst 相同)
LinkedList<String> stack = new LinkedList<>();stack.push("A");
stack.push("B");
stack.push("C");
System.out.println(stack); // [C, B, A] 栈顶是C
3.2 元素删除方法
3.2.1 基本删除操作
remove()
: 删除并返回头部元素remove(int index)
: 删除指定位置元素并返回remove(Object o)
: 删除第一个匹配的元素,返回是否删除成功
LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C", "B"));// 删除头部元素
String first = list.remove();
System.out.println(first); // A
System.out.println(list); // [B, C, B]// 删除指定位置元素
String removed = list.remove(1);
System.out.println(removed); // C
System.out.println(list); // [B, B]// 删除指定元素
boolean isRemoved = list.remove("B");
System.out.println(isRemoved); // true
System.out.println(list); // [B]
3.2.2 首尾删除操作(Deque 特性)
removeFirst()
: 删除并返回头部元素removeLast()
: 删除并返回尾部元素removeFirstOccurrence(Object o)
:删除第一次出现的元素removeLastOccurrence(Object o)
: 删除最后一次出现的元素poll()
: 删除并返回头部元素(与 remove 类似,队列为空时返回 null)pollFirst()
: 删除并返回头部元素(与 removeFirst 类似)pollLast()
: 删除并返回尾部元素(与 removeLast 类似)
LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C", "B", "D"));// 删除第一次出现的B
list.removeFirstOccurrence("B");
System.out.println(list); // [A, C, B, D]// 删除最后一次出现的B
list.removeLastOccurrence("B");
System.out.println(list); // [A, C, D]// poll系列方法
String head = list.poll(); // 移除头部
String last = list.pollLast(); // 移除尾部
System.out.println(head); // A
System.out.println(last); // D
System.out.println(list); // [C]
3.2.3 栈操作
pop()
:弹出栈顶元素(实际上是删除头部元素,与 removeFirst 相同)
LinkedList<String> stack = new LinkedList<>(Arrays.asList("A", "B", "C"));String top = stack.pop();
System.out.println(top); // A
System.out.println(stack); // [B, C]
3.3 元素查询方法
3.3.1 基本查询操作
get(int index)
: 返回指定位置的元素getFirst()
: 返回头部元素getLast()
: 返回尾部元素contains(Object o)
: 判断是否包含指定元素indexOf(Object o)
: 返回第一个匹配元素的索引lastIndexOf(Object o)
: 返回最后一个匹配元素的索引
LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C", "B"));// 获取指定位置元素
String element = list.get(2);
System.out.println(element); // C// 获取首尾元素
System.out.println(list.getFirst()); // A
System.out.println(list.getLast()); // B// 查找元素位置
System.out.println(list.indexOf("B")); // 1
System.out.println(list.lastIndexOf("B"));// 3// 判断是否包含
System.out.println(list.contains("C")); // true
System.out.println(list.contains("D")); // false
3.3.2 队列风格查询(Deque 特性)
element()
: 返回头部元素(队列为空时抛异常)peek()
: 返回头部元素(队列为空时返回 null)peekFirst()
: 返回头部元素(队列为空时返回 null)peekLast()
: 返回尾部元素(队列为空时返回 null)
LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C"));// 队列风格查询
System.out.println(list.element()); // A
System.out.println(list.peek()); // A
System.out.println(list.peekFirst()); // A
System.out.println(list.peekLast()); // C// 空列表测试
LinkedList<String> emptyList = new LinkedList<>();
System.out.println(emptyList.peek()); // null
// System.out.println(emptyList.element()); // 抛出NoSuchElementException
3.4 元素修改方法
set(int index, E element)
:替换指定位置的元素并返回旧元素
LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C"));// 替换元素
String old = list.set(1, "X");
System.out.println(old); // B
System.out.println(list); // [A, X, C]
3.5 其他常用方法
size()
: 返回元素数量clear()
: 清空所有元素clone()
: 返回浅拷贝toArray()
: 转换为数组toArray(T[] a)
: 转换为指定类型数组descendingIterator()
: 返回逆序迭代器listIterator(int index)
: 返回从指定位置开始的列表迭代器
LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C"));// 基本信息
System.out.println(list.size()); // 3// 克隆
LinkedList<String> cloned = (LinkedList<String>) list.clone();
System.out.println(cloned); // [A, B, C]// 转换为数组
Object[] array = list.toArray();
String[] strArray = list.toArray(new String[0]);// 逆序迭代
Iterator<String> descIt = list.descendingIterator();
while (descIt.hasNext()) {System.out.print(descIt.next() + " "); // C B A
}// 列表迭代器
ListIterator<String> lit = list.listIterator(1);
while (lit.hasNext()) {System.out.print(lit.next() + " "); // B C
}// 清空列表
list.clear();
System.out.println(list.size()); // 0
四、LinkedList 与 ArrayList 对比分析
特性 | LinkedList | ArrayList |
---|---|---|
底层结构 | 双向链表 | 动态数组 |
随机访问 | 慢(O (n)) | 快(O (1)) |
头部插入 / 删除 | 快(O (1)) | 慢(O (n)) |
尾部插入 / 删除 | 快(O (1)) | 快(O (1)) |
中间插入 / 删除 | 慢(O (n)) | 慢(O (n)) |
内存占用 | 较高(每个节点需要额外存储前后引用) | 较低(连续存储) |
扩容机制 | 无需扩容 | 需要扩容(通常扩容为 1.5 倍) |
选择建议:
- 需要频繁随机访问时,选择 ArrayList
- 需要频繁在首尾操作元素时,选择 LinkedList
- 需要频繁在中间插入删除时,视具体情况而定( LinkedList 需要先遍历到指定位置,也是 O (n) 操作)
五、线程安全问题
LinkedList 是非线程安全的集合类。在多线程环境下,若有一个线程修改了集合结构(添加或删除元素),其他线程在迭代时可能会抛出 ConcurrentModificationException
。
线程安全解决方案:
1、使用 Collections.synchronizedList()
包装:
List<String> syncList = Collections.synchronizedList(new LinkedList<>());
2、使用 java.util.concurrent
包下的 ConcurrentLinkedDeque
(推荐):
Deque<String> concurrentDeque = new ConcurrentLinkedDeque<>();
六、迭代器与快速失败机制
LinkedList 的迭代器(包括 iterator 和 listIterator)采用快速失败(fail-fast)机制:当迭代器创建后,如果集合结构被修改(除了通过迭代器自身的方法),迭代器会立即抛出 ConcurrentModificationException
。
LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C"));Iterator<String> it = list.iterator();
while (it.hasNext()) {String s = it.next();if (s.equals("B")) {// 使用迭代器的remove方法是安全的it.remove(); // 直接使用list的remove方法会抛出异常// list.remove("B"); // 抛出ConcurrentModificationException}
}
System.out.println(list); // [A, C]
注意:
快速失败机制只是尽力而为,不能保证一定能检测到并发修改,因此不能依赖它来处理并发问题,只能用于调试。
迭代方对比:
LinkedList<String> list = new LinkedList<>();
// 添加元素...// 方式1:for循环通过索引访问 - 性能最差
for (int i = 0; i < list.size(); i++) {String s = list.get(i); // 每次get都要从头或尾遍历
}// 方式2:增强for循环 - 内部使用迭代器
for (String s : list) {// 操作元素
}// 方式3:显式使用迭代器 - 可以控制遍历过程
Iterator<String> it = list.iterator();
while (it.hasNext()) {String s = it.next();// 可以调用it.remove()
}// 方式4:使用forEach方法(Java 8+)
list.forEach(s -> {// 操作元素
});
- 避免使用 for 循环通过索引访问 LinkedList
- 优先使用迭代器或增强 for 循环
- 需要删除元素时,使用迭代器的 remove () 方法
七、常见问题与注意事项
7.1 关于 null 元素
LinkedList 允许添加 null 元素,并且可以正确处理:
LinkedList<String> list = new LinkedList<>();
list.add(null);
list.add("A");
list.add(null);System.out.println(list.contains(null)); // true
System.out.println(list.indexOf(null)); // 0
System.out.println(list.lastIndexOf(null)); // 2
但需注意:在使用indexOf()
和lastIndexOf()
时,null 值会被正确识别和比较。
7.2 序列化问题
LinkedList 实现了 Serializable 接口,但它的first
、last
和size
字段都被声明为transient
,这意味着默认序列化机制不会序列化这些字段。
LinkedList 通过重写writeObject()
和readObject()
方法实现了自定义序列化,只序列化元素本身,而不是整个节点结构,这样可以减小序列化后的大小。
7.3 克隆问题
LinkedList 的clone()
方法返回的是浅拷贝:
LinkedList<List<String>> list = new LinkedList<>();
list.add(new ArrayList<>());LinkedList<List<String>> cloned = (LinkedList<List<String>>) list.clone();
// 克隆后的列表包含相同的元素引用
System.out.println(list.get(0) == cloned.get(0)); // true
这意味着:
- 基本类型元素会被复制
- 对象元素只会复制引用,不会创建新对象
- 修改克隆列表中的对象元素会影响原列表