1.1 LinkedList 简介
LinkedList
是 Java 集合框架中的一个类,它实现了 List
、Deque
和 Queue
接口,底层基于双向链表实现。
LinkedList的底层是双向链表结构,由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引⽤将节点连接起来了,因此在在任意位置插⼊或者删除元素时,不需要搬移元素,效率⽐较⾼。
在集合框架中,LinkedList也实现了List接⼝,具体如下:
【说明】 1. LinkedList实现了List接⼝ 2. LinkedList的底层使⽤了双向链表 3. LinkedList没有实现RandomAccess接⼝,因此LinkedList不⽀持随机访问 4. LinkedList的任意位置插⼊和删除元素时效率⽐较⾼,时间复杂度为O(1) 5. LinkedList⽐较适合任意位置插⼊的场景
1.2 LinkedList 的实现原理
LinkedList
的底层是一个双向链表,由节点(Node)组成。每个节点包含数据和指向前后节点的引用。
1.3 LinkedList 与 ArrayList 的区别
LinkedList
是链表,ArrayList
是动态数组。ArrayList
随机访问快,LinkedList
插入和删除快。LinkedList
因为维护节点引用,内存开销更大。代码示例:创建和使用 LinkedList
import java.util.LinkedList;
public class LinkedListExample {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("A");
list.add("B");
list.add("C");
list.addFirst("Start");
list.addLast("End");
System.out.println("LinkedList: " + list);
list.remove("B");
System.out.println("After removing 'B': " + list);
System.out.println("First Element: " + list.getFirst());
System.out.println("Last Element: " + list.getLast());
}
}
2. 链表基础
2.1 链表的定义与种类 链表是一种通过指针将数据节点连接起来的数据结构,链表是⼀种物理存储结构上⾮连续存储结构,数据元素的逻辑顺序是通过链表中的引⽤链接次序实现的 。主要有以下几种:
注意: 1.从上图可看出,链式结构在逻辑上是连续的,但是在物理上不一定连续 2.现实中的结点一般都是从堆上申请出来的 3.从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续
重点掌握两种 1.⽆头单向⾮循环链表:结构简单,⼀般不会单独⽤来存数据。实际中更多是作为其他数据结构的⼦结构,如哈希桶、图的邻接表等等。另外这种结构在笔试⾯试中出现很多。
2.⽆头双向链表:在Java的集合框架库中LinkedList底层实现就是⽆头双向循环链表。
2.2 单链表与双链表的区别
1.结构定义 单链表是由一系列节点组成的数据结构。每个节点包含两个部分,一个是数据元素本身,另一个是指向下一个节点的指针(引用)。 例如,用Java语言简单定义一个单链表节点类可以是这样:
class ListNode {
int val; // 数据元素,这里以整数为例
ListNode next; // 指向下一个节点的引用
ListNode(int val) {
this.val = val;
this.next = null;
}
}
整个单链表就像是一条单向的链条,从表头开始,通过每个节点的next
指针依次访问下一个节点,直到最后一个节点的next
指针为null
,表示链表的末尾。
- 双链表的节点除了包含数据元素和指向下一个节点的指针外,还包含一个指向前一个节点的指针。
- 以下是用Java定义双链表节点类的示例:
class DoubleListNode {
int val;
DoubleListNode prev;
DoubleListNode next;
DoubleListNode(int val) {
this.val = val;
this.prev = null;
this.next = null;
}
}
- 双链表就像一个双向的通道,既可以从表头顺着`next`指针向后遍历,也可以从表尾顺着`prev`指针向前遍历。
next
指针逐个访问下一个节点,直到到达链表尾部。ListNode head = new ListNode(1);
// 假设已经构建好链表,这里简单示例添加一个节点
ListNode second = new ListNode(2);
head.next = second;
ListNode current = head;
while (current!= null) {
System.out.println(current.val);
current = current.next;
}
双链表:
- 可以双向遍历。既可以从头部开始,通过`next`指针向后遍历,也可以从尾部开始,通过`prev`指针向前遍历。
- 例如,遍历双链表向后的代码如下:
DoubleListNode headDouble = new DoubleListNode(1);
DoubleListNode secondDouble = new DoubleListNode(2);
headDouble.next = secondDouble;
secondDouble.prev = headDouble;
DoubleListNode currentDouble = headDouble;
while (currentDouble!= null) {
System.out.println(currentDouble.val);
currentDouble = currentDouble.next;
}
- 向前遍历的代码如下:
DoubleListNode tail = secondDouble;
while (tail!= null) {
System.out.println(tail.val);
tail = tail.prev;
}
p
之后插入节点newNode
,步骤如下:newNode
的next
指针指向p
节点的下一个节点(即newNode.next = p.next
),然后将p
节点的next
指针指向newNode
(即p.next = newNode
)。// 在单链表节点p之后插入newNode
ListNode p = head;
ListNode newNode = new ListNode(3);
newNode.next = p.next;
p.next = newNode;
但是如果要在指定节点之前插入新节点,需要先遍历链表找到该节点的前驱节点,这会比较复杂,尤其是在没有额外的头节点辅助的情况下。
prev
指针,操作更加方便。在节点p
之后插入newNode
,步骤如下:newNode
的next
指针指向p
节点的下一个节点(即newNode.next = p.next
),然后将p
节点的下一个节点(假设为q
)的prev
指针指向newNode
(即q.prev = newNode
),接着将p
节点的next
指针指向newNode
(即p.next = newNode
),最后将newNode
的prev
指针指向p
(即newNode.prev = p
)。prev
指针找到前驱节点进行操作。p
,需要先找到p
的前驱节点(假设为q
),然后将q
的next
指针指向p
的下一个节点(即q.next = p.next
)。p
,可以直接通过p
的prev
指针找到前驱节点(假设为q
),将q
的next
指针指向p
的下一个节点(即q.next = p.next
),同时通过p
的next
指针找到后继节点(假设为r
),将r
的prev
指针指向q
(即r.prev = q
)。2.3 循环链表与普通链表
普通链表由节点依次连接而成,有明确的头节点,最后一个节点的指针指向空,以此标识链表的结尾。例如单链表通过各节点的指针顺序串联数据。而循环链表则是一种特殊形式,它的最后一个节点指针并非指向空,而是指向头节点,从而形成一个闭环结构。在遍历方面,普通链表遇空指针停止,循环链表则需特定条件控制遍历次数以避免死循环。插入操作时,普通链表需关注前驱节点处理,循环链表除插入逻辑外,还得维护循环特性,二者在不同应用场景下各有优势。
3. Java LinkedList 的常见操作
3.1 添加元素
LinkedList
提供了多种方法来添加元素,常用的方法包括:
代码示例:添加元素
import java.util.LinkedList;
public class LinkedListAddExample {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("A"); // 添加到尾部
list.addFirst("Start"); // 添加到头部
list.addLast("End"); // 添加到尾部
list.add(1, "Middle"); // 指定位置添加
System.out.println("LinkedList: " + list);
}
}
3.2 删除元素
LinkedList
提供的方法来删除元素:
代码示例:删除元素
import java.util.LinkedList;
public class LinkedListRemoveExample {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
System.out.println("Original List: " + list);
list.remove("B"); // 删除元素 B
System.out.println("After removing 'B': " + list);
list.remove(1); // 删除索引为 1 的元素
System.out.println("After removing index 1: " + list);
list.removeFirst(); // 删除头部元素
System.out.println("After removing first: " + list);
list.removeLast(); // 删除尾部元素
System.out.println("After removing last: " + list);
}
}
3.3 查找元素
LinkedList
提供的方法来查找元素:
代码示例:查找元素
import java.util.LinkedList;
public class LinkedListFindExample {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("B");
System.out.println("LinkedList: " + list);
System.out.println("Element at index 1: " + list.get(1)); // 获取索引为1的元素
System.out.println("First Element: " + list.getFirst()); // 获取头部元素
System.out.println("Last Element: " + list.getLast()); // 获取尾部元素
System.out.println("Index of 'B': " + list.indexOf("B")); // 查找 B 的第一次出现位置
System.out.println("Last Index of 'B': " + list.lastIndexOf("B")); // 查找 B 的最后一次出现位置
System.out.println("Contains 'C': " + list.contains("C")); // 检查是否包含 C
}
}
3.4 修改元素
修改元素通常使用 set(int index, E element)
方法,可以替换指定索引处的元素。
代码示例:修改元素
import java.util.LinkedList;
public class LinkedListModifyExample {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("A");
list.add("B");
list.add("C");
System.out.println("Original List: " + list);
list.set(1, "X"); // 将索引为1的元素替换为 "X"
System.out.println("After modification: " + list);
}
}
3.5 遍历元素
LinkedList
的遍历可以使用 for
循环、增强 for
循环以及迭代器。
代码示例:遍历元素
import java.util.LinkedList;
import java.util.Iterator;
public class LinkedListTraverseExample {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("A");
list.add("B");
list.add("C");
// 使用 for 循环
System.out.println("Using for loop:");
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + " ");
}
System.out.println();
// 使用增强的 for 循环
System.out.println("Using enhanced for loop:");
for (String element : list) {
System.out.print(element + " ");
}
System.out.println();
// 使用迭代器
System.out.println("Using iterator:");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
System.out.println();
}
}
4. Java LinkedList 的高级操作
4.1 迭代器的使用
LinkedList
支持两种迭代器:
代码示例:使用 Iterator 和 ListIterator
import java.util.LinkedList;
import java.util.Iterator;
import java.util.ListIterator;
public class LinkedListIteratorExample {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("A");
list.add("B");
list.add("C");
// 使用 Iterator 单向遍历
System.out.println("Using Iterator:");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
System.out.println();
// 使用 ListIterator 双向遍历
System.out.println("Using ListIterator (forward):");
ListIterator<String> listIterator = list.listIterator();
while (listIterator.hasNext()) {
System.out.print(listIterator.next() + " ");
}
System.out.println();
System.out.println("Using ListIterator (backward):");
while (listIterator.hasPrevious()) {
System.out.print(listIterator.previous() + " ");
}
System.out.println();
}
}
4.2 Queue 和 Deque 接口中的操作
LinkedList
实现了 Deque
接口,因此它可以作为双端队列使用。
常用操作:
offer(E e)
:将元素添加到队列尾部。poll()
:移除并返回队列头部的元素。peek()
:返回队列头部的元素但不移除。offerFirst(E e)
:在头部添加元素。offerLast(E e)
:在尾部添加元素。pollFirst()
:移除并返回头部的元素。pollLast()
:移除并返回尾部的元素。代码示例:作为队列使用
import java.util.LinkedList;
import java.util.Queue;
public class LinkedListQueueExample {
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
// 添加元素
queue.offer(1);
queue.offer(2);
queue.offer(3);
System.out.println("Queue: " + queue);
// 查看头部元素
System.out.println("Peek: " + queue.peek());
// 移除头部元素
System.out.println("Poll: " + queue.poll());
System.out.println("Queue after poll: " + queue);
}
}
代码示例:作为双端队列使用
import java.util.Deque;
import java.util.LinkedList;
public class LinkedListDequeExample {
public static void main(String[] args) {
Deque<String> deque = new LinkedList<>();
// 双端操作
deque.offerFirst("A");
deque.offerLast("B");
deque.offerFirst("C");
System.out.println("Deque: " + deque);
// 头部和尾部操作
System.out.println("Poll First: " + deque.pollFirst());
System.out.println("Poll Last: " + deque.pollLast());
System.out.println("Deque after poll: " + deque);
}
}
4.3 使用 Collections 对 LinkedList 进行排序和操作
LinkedList
可以使用 Collections
工具类进行排序、反转等操作。
代码示例:对 LinkedList 排序
import java.util.LinkedList;
import java.util.Collections;
public class LinkedListSortExample {
public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<>();
list.add(5);
list.add(2);
list.add(8);
list.add(1);
System.out.println("Original List: " + list);
// 排序
Collections.sort(list);
System.out.println("Sorted List: " + list);
// 反转
Collections.reverse(list);
System.out.println("Reversed List: " + list);
// 随机打乱
Collections.shuffle(list);
System.out.println("Shuffled List: " + list);
}
}
4.4 克隆 LinkedList
使用 clone()
方法可以创建 LinkedList 的浅拷贝。
代码示例:克隆 LinkedList
import java.util.LinkedList;
public class LinkedListCloneExample {
public static void main(String[] args) {
LinkedList<String> original = new LinkedList<>();
original.add("A");
original.add("B");
original.add("C");
LinkedList<String> cloned = (LinkedList<String>) original.clone();
System.out.println("Original List: " + original);
System.out.println("Cloned List: " + cloned);
// 修改克隆的列表不会影响原列表
cloned.add("D");
System.out.println("Modified Cloned List: " + cloned);
System.out.println("Original List after modification: " + original);
}
}
4.5 将 LinkedList 转为其他集合类型
LinkedList
可以轻松地转为 ArrayList
或其他集合类型。
代码示例:转为 ArrayList
import java.util.LinkedList;
import java.util.ArrayList;
public class LinkedListToArrayListExample {
public static void main(String[] args) {
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("A");
linkedList.add("B");
linkedList.add("C");
// 转为 ArrayList
ArrayList<String> arrayList = new ArrayList<>(linkedList);
System.out.println("LinkedList: " + linkedList);
System.out.println("ArrayList: " + arrayList);
}
}
5. 链表的基本操作与算法
5.1 单链表的反转
反转单链表的核心思想是改变节点的 next
指针的指向。
代码示例:反转单链表
public class ReverseLinkedList {
static class Node {
int data;
Node next;
Node(int data) {
this.data = data;
}
}
public static Node reverse(Node head) {
Node prev = null;
Node current = head;
while (current != null) {
Node next = current.next;
current.next = prev;
prev = current;
current = next;
}
return prev;
}
public static void printList(Node head) {
Node temp = head;
while (temp != null) {
System.out.print(temp.data + " -> ");
temp = temp.next;
}
System.out.println("null");
}
public static void main(String[] args) {
Node head = new Node(1);
head.next = new Node(2);
head.next.next = new Node(3);
System.out.println("Original List:");
printList(head);
head = reverse(head);
System.out.println("Reversed List:");
printList(head);
}
}
5.2 链表中环的检测 使用快慢指针(Floyd 判圈法)检测链表中是否存在环。
代码示例:检测环
public class DetectCycle {
static class Node {
int data;
Node next;
Node(int data) {
this.data = data;
}
}
public static boolean hasCycle(Node head) {
Node slow = head;
Node fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
return true;
}
}
return false;
}
public static void main(String[] args) {
Node head = new Node(1);
head.next = new Node(2);
head.next.next = new Node(3);
head.next.next.next = head; // Creates a cycle
System.out.println("Has Cycle: " + hasCycle(head));
}
}
6. LinkedList 的性能分析与优化
6.1 时间复杂度分析
LinkedList
的操作性能受链表结构的限制,具体复杂度如下:
操作 | 时间复杂度 | 说明 |
---|---|---|
添加元素 | ||
- 头部插入 | O(1) | 直接修改头节点指针即可。 |
- 尾部插入 | O(1) | 如果有尾指针,直接修改尾节点指针;否则需要遍历到尾部。 |
- 指定位置插入 | O(n) | 需要遍历链表找到指定位置。 |
删除元素 | ||
- 头部删除 | O(1) | 修改头节点指针即可。 |
- 尾部删除 | O(n) | 需要遍历找到尾部的前一个节点。 |
- 指定位置删除 | O(n) | 需要遍历找到该位置。 |
访问元素 | O(n) | 需要从头开始逐个遍历找到指定位置。 |
搜索元素 | O(n) | 需要从头到尾遍历链表。 |
总结: 对于频繁的插入、删除操作(尤其是头部或尾部操作),
LinkedList
性能优于ArrayList
。 对于频繁的随机访问或搜索操作,ArrayList
是更优选择。
6.2 内存消耗分析
LinkedList
的内存使用较高,因为每个节点不仅存储数据,还需要额外存储前驱和后继的指针。
特性 | LinkedList | ArrayList |
---|---|---|
存储结构 | 双向链表 | 动态数组 |
节点开销 | 每个节点有两个额外指针 | 仅存储数据 |
空间利用率 | 较低 | 较高 |
示例:分析内存消耗
import java.util.LinkedList;
import java.util.ArrayList;
public class MemoryUsageExample {
public static void main(String[] args) {
LinkedList<Integer> linkedList = new LinkedList<>();
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
linkedList.add(i);
arrayList.add(i);
}
System.out.println("LinkedList size: " + linkedList.size());
System.out.println("ArrayList size: " + arrayList.size());
// 虽然无法直接显示内存使用,但 LinkedList 的开销更高。
}
}
6.3 性能优化方法
选择适合的场景:
LinkedList
时应避免频繁的随机访问操作。LinkedList
。减少遍历操作:
get(index)
来访问元素,可以使用 Iterator
或增强的 for
循环提高效率。示例:用迭代器代替索引遍历
import java.util.LinkedList;
import java.util.Iterator;
public class OptimizedTraversal {
public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<>();
for (int i = 0; i < 1000; i++) {
list.add(i);
}
// 遍历方式优化
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer value = iterator.next();
// 处理数据
}
}
}
预估链表大小:
LinkedList
构建大规模数据结构,尽量减少动态扩展,预先分配节点空间。结合其他数据结构:
HashMap
和 LinkedList
,例如实现 LRU 缓存。6.4 性能测试与比较
代码示例:LinkedList 与 ArrayList 性能对比
import java.util.LinkedList;
import java.util.ArrayList;
public class PerformanceTest {
public static void main(String[] args) {
LinkedList<Integer> linkedList = new LinkedList<>();
ArrayList<Integer> arrayList = new ArrayList<>();
long startTime, endTime;
// 测试插入性能
startTime = System.nanoTime();
for (int i = 0; i < 100000; i++) {
linkedList.add(0, i);
}
endTime = System.nanoTime();
System.out.println("LinkedList insertion time: " + (endTime - startTime) + " ns");
startTime = System.nanoTime();
for (int i = 0; i < 100000; i++) {
arrayList.add(0, i);
}
endTime = System.nanoTime();
System.out.println("ArrayList insertion time: " + (endTime - startTime) + " ns");
// 测试访问性能
startTime = System.nanoTime();
for (int i = 0; i < linkedList.size(); i++) {
linkedList.get(i);
}
endTime = System.nanoTime();
System.out.println("LinkedList access time: " + (endTime - startTime) + " ns");
startTime = System.nanoTime();
for (int i = 0; i < arrayList.size(); i++) {
arrayList.get(i);
}
endTime = System.nanoTime();
System.out.println("ArrayList access time: " + (endTime - startTime) + " ns");
}
}
测试结果:
LinkedList
优于 ArrayList
,尤其是在头部插入时。ArrayList
明显优于 LinkedList
,因为它支持随机访问。7. LinkedList 的应用场景
LinkedList
是基于双向链表实现的,适用于某些特定的应用场景。以下列出 LinkedList
的主要应用及实现方法。
7.1 实现队列 (Queue)
LinkedList
实现了 Queue
接口,因此可以直接作为队列使用,支持先进先出的操作。
常用方法:
代码示例:实现队列
import java.util.LinkedList;
import java.util.Queue;
public class LinkedListQueueExample {
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
// 添加元素到队列
queue.offer(10);
queue.offer(20);
queue.offer(30);
System.out.println("Queue: " + queue);
// 获取并移除头部元素
System.out.println("Poll: " + queue.poll());
// 获取头部元素但不移除
System.out.println("Peek: " + queue.peek());
System.out.println("Queue after operations: " + queue);
}
}
7.2 实现栈 (Stack)
LinkedList
也可以用作栈的实现,支持后进先出的操作。可以使用以下方法:
代码示例:实现栈
import java.util.LinkedList;
public class LinkedListStackExample {
public static void main(String[] args) {
LinkedList<Integer> stack = new LinkedList<>();
// 压栈
stack.push(10);
stack.push(20);
stack.push(30);
System.out.println("Stack: " + stack);
// 弹栈
System.out.println("Pop: " + stack.pop());
// 查看栈顶元素
System.out.println("Peek: " + stack.peek());
System.out.println("Stack after operations: " + stack);
}
}
7.3 数据历史记录的存储
LinkedList
的双向链表特性,支持快速遍历前后节点,因此适用于实现历史记录功能,如浏览器的前进和后退。
代码示例:实现简单的历史记录功能
import java.util.LinkedList;
public class BrowserHistory {
private LinkedList<String> history = new LinkedList<>();
private int current = -1;
public void visit(String url) {
while (history.size() > current + 1) {
history.removeLast(); // 清除前进历史
}
history.add(url);
current++;
System.out.println("Visited: " + url);
}
public void back() {
if (current > 0) {
current--;
System.out.println("Back to: " + history.get(current));
} else {
System.out.println("No more back history.");
}
}
public void forward() {
if (current < history.size() - 1) {
current++;
System.out.println("Forward to: " + history.get(current));
} else {
System.out.println("No more forward history.");
}
}
public static void main(String[] args) {
BrowserHistory browser = new BrowserHistory();
browser.visit("google.com");
browser.visit("youtube.com");
browser.back();
browser.visit("github.com");
browser.back();
browser.forward();
}
}
8. 链表的代码实现
8.1 单链表的手动实现 代码示例:实现单链表
class MyLinkedList {
private Node head;
private static class Node {
int data;
Node next;
Node(int data) {
this.data = data;
}
}
public void add(int data) {
if (head == null) {
head = new Node(data);
return;
}
Node temp = head;
while (temp.next != null) {
temp = temp.next;
}
temp.next = new Node(data);
}
public void printList() {
Node temp = head;
while (temp != null) {
System.out.print(temp.data + " -> ");
temp = temp.next;
}
System.out.println("null");
}
public static void main(String[] args) {
MyLinkedList list = new MyLinkedList();
list.add(1);
list.add(2);
list.add(3);
list.printList();
}
}
9. 常见问题与调试技巧
9.1 空指针异常(NullPointerException)
问题描述:
当操作一个空的 LinkedList
时(例如访问头部或尾部元素),会抛出 NullPointerException
。
示例代码:
import java.util.LinkedList;
public class NullPointerExample {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
// 空列表访问会导致 NullPointerException
System.out.println(list.getFirst());
}
}
解决方法:
在操作之前检查列表是否为空:
if (!list.isEmpty()) {
System.out.println(list.getFirst());
} else {
System.out.println("List is empty.");
}
使用带有默认返回值的方法(如 peekFirst
和 peekLast
),这些方法不会抛出异常:
System.out.println(list.peekFirst()); // 返回 null 而非抛出异常
9.2 并发修改异常(ConcurrentModificationException)
问题描述:
在遍历 LinkedList
的同时修改其内容(例如添加或删除元素),会抛出 ConcurrentModificationException
。
示例代码:
import java.util.LinkedList;
public class ConcurrentModificationExample {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("A");
list.add("B");
list.add("C");
for (String element : list) {
if (element.equals("B")) {
list.remove(element); // 在遍历时修改列表
}
}
}
}
解决方法:
使用 迭代器 的 remove()
方法:
import java.util.Iterator;
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
if (element.equals("B")) {
iterator.remove(); // 使用迭代器的 remove 方法
}
}
使用 CopyOnWriteArrayList
或其他线程安全的集合来避免并发问题。
9.3 性能问题
问题描述:
对 LinkedList
进行频繁的随机访问或搜索操作时,性能可能会很低。
示例代码:
LinkedList<Integer> list = new LinkedList<>();
for (int i = 0; i < 100000; i++) {
list.add(i);
}
// 随机访问
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i)); // 低效:每次 get 都需要从头遍历
}
解决方法:
ArrayList
替代 LinkedList
。9.4 死循环问题
问题描述: 在手动实现链表时,可能会不小心创建一个环,导致死循环。
示例代码:
class Node {
int data;
Node next;
Node(int data) {
this.data = data;
}
}
public class InfiniteLoopExample {
public static void main(String[] args) {
Node head = new Node(1);
Node second = new Node(2);
head.next = second;
second.next = head; // 创建一个循环
Node current = head;
while (current != null) {
System.out.println(current.data);
current = current.next; // 无限循环
}
}
}
解决方法:
使用快慢指针检测环:
public static boolean hasCycle(Node head) {
Node slow = head;
Node fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
return true; // 检测到环
}
}
return false;
}
小心修改链表的 next
指针,避免手动制造环。
9.5 IndexOutOfBoundsException
问题描述:
在尝试访问 LinkedList
中不存在的索引时,会抛出 IndexOutOfBoundsException
。
示例代码:
import java.util.LinkedList;
public class IndexOutOfBoundsExample {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("A");
System.out.println(list.get(5)); // 超出范围
}
}
解决方法:
确保访问的索引有效:
if (index >= 0 && index < list.size()) {
System.out.println(list.get(index));
} else {
System.out.println("Index out of bounds.");
}
使用异常捕获机制:
try {
System.out.println(list.get(5));
} catch (IndexOutOfBoundsException e) {
System.out.println("Invalid index: " + e.getMessage());
}
9.6 内存泄漏问题
问题描述: 长时间持有链表的引用,或者链表节点之间存在循环引用,可能导致内存泄漏。
解决方法:
使用弱引用(WeakReference
)来避免不必要的对象持有。
手动清理链表不再需要的节点:
list.clear(); // 释放所有元素
9.7 链表的深拷贝问题
问题描述:
默认的 clone()
方法只进行浅拷贝,对于复杂对象可能会引发问题。
示例代码:
LinkedList<Node> list = new LinkedList<>();
list.add(new Node(1));
LinkedList<Node> clonedList = (LinkedList<Node>) list.clone();
clonedList.get(0).data = 100; // 修改克隆后的数据会影响原链表
解决方法:
实现深拷贝:
LinkedList<Node> deepClone = new LinkedList<>();
for (Node node : list) {
deepClone.add(new Node(node.data)); // 手动复制节点
}
10. 总结与扩展
10.1 LinkedList 在实际开发中的适用性
Queue
)或双端队列(Deque
)使用。10.2 链表的扩展实现
实现栈(Stack)功能
LinkedList
可以很方便地实现栈,利用 addFirst()
和 removeFirst()
模拟栈的 push
和 pop
操作。代码示例:使用 LinkedList 实现栈
import java.util.LinkedList;
public class LinkedListStack {
private LinkedList<Integer> stack = new LinkedList<>();
public void push(int value) {
stack.addFirst(value);
}
public int pop() {
return stack.removeFirst();
}
public int peek() {
return stack.getFirst();
}
public boolean isEmpty() {
return stack.isEmpty();
}
public static void main(String[] args) {
LinkedListStack stack = new LinkedListStack();
stack.push(10);
stack.push(20);
System.out.println(stack.pop()); // 输出 20
System.out.println(stack.peek()); // 输出 10
}
}
实现队列(Queue)功能
LinkedList
本身实现了 Queue
接口,天然适合用于队列操作。实现 LRU 缓存(最近最少使用缓存)
LinkedHashMap
和 LinkedList
的组合,可以实现一个简单的 LRU 缓存。代码示例:LRU 缓存
import java.util.LinkedHashMap;
import java.util.Map;
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int capacity;
public LRUCache(int capacity) {
super(capacity, 0.75f, true);
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity;
}
public static void main(String[] args) {
LRUCache<Integer, String> cache = new LRUCache<>(3);
cache.put(1, "A");
cache.put(2, "B");
cache.put(3, "C");
cache.get(1); // 使用键 1
cache.put(4, "D"); // 淘汰键 2
System.out.println(cache); // 输出 {3=C, 1=A, 4=D}
}
}
跳表(Skip List)
10.3 学习和实践资源推荐
LinkedList
提供的所有方法及其实现原理。LinkedList
在集合框架中的地位。LRU Cache
实现。