java怎么创建链表的
- 后端开发
- 2025-08-07
- 4
基础概念与设计思路
1 链表类型选择
| 类型 | 特点 | 适用场景 |
|---|---|---|
| 单链表 | 每个节点仅保存后继节点的引用 | 简单遍历、频繁增删尾部 |
| 双向链表 | 每个节点同时保存前驱和后继节点的引用 | 双向遍历、频繁增删任意位置 |
| 循环链表 | 首尾相连形成环形 | 轮转调度、缓冲区管理 |
| 带头节点链表 | 增加一个不存储数据的虚拟头节点 | 简化边界条件处理 |
2 核心组件设计
- Node类:封装数据(data)和下一个节点的引用(next)
- LinkedList类:管理链表整体行为(初始化、插入、删除、遍历)
- 泛型支持:通过<E>定义通用类型参数,提升代码复用性
单链表完整实现步骤
1 定义节点类 Node<E>
// 节点类模板 class Node<E> { E data; // 存储的数据 Node<E> next; // 指向下一个节点的引用 // 构造函数 public Node(E data) { this.data = data; this.next = null; // 新节点默认无后续节点 } }
️ 关键点:next字段初始化为null,表示新节点独立存在。
2 构建链表主体类 SinglyLinkedList<E>
public class SinglyLinkedList<E> { private Node<E> head; // 链表头节点 private int size; // 链表长度计数器 // 构造函数:创建空链表 public SinglyLinkedList() { head = null; // 初始时链表为空 size = 0; } }
优化建议:可改为带头节点的设计(见下文扩展部分),统一空/非空状态的处理逻辑。

3 核心方法实现
| 方法 | 功能描述 | 时间复杂度 | 关键逻辑 |
|---|---|---|---|
| add(E element) | 在链表末尾追加元素 | O(n) | 遍历至最后一个节点,将其next指向新节点 |
| addAt(int index, E element) | 在指定位置插入元素 | O(n) | 找到index-1位置的节点,修改其next指向新节点 |
| remove(int index) | 删除指定位置的元素 | O(n) | 找到index-1位置的节点,跳过待删除节点,重新连接前后节点 |
| contains(E element) | 判断元素是否存在 | O(n) | 遍历链表逐个比对元素 |
| get(int index) | 获取指定位置的元素 | O(n) | 遍历至目标位置返回数据 |
| size() | 返回链表长度 | O(1) | 直接返回size变量 |
| isEmpty() | 判断链表是否为空 | O(1) | 检查head == null或size == 0 |
示例代码片段:add方法实现
public void add(E element) { Node<E> newNode = new Node<>(element); if (head == null) { // 链表为空时直接设为头节点 head = newNode; } else { // 非空时找到最后一个节点 Node<E> current = head; while (current.next != null) { current = current.next; } current.next = newNode; // 将新节点挂在末尾 } size++; // 更新长度计数器 }
注意:上述实现未使用尾指针优化,导致add操作需要遍历整个链表,若需高频尾部插入,应增加tail字段维护尾节点引用。
进阶优化方案
1 带头节点的链表设计
// 修改后的链表结构 private Node<E> dummyHead; // 虚拟头节点,不存储实际数据 private int size; public SinglyLinkedList() { dummyHead = new Node<>(null); // 初始化虚拟头节点 size = 0; }
优势:
- 消除空链表的特殊处理(head永远不为null)
- 统一插入/删除操作的逻辑(无需判断是否是第一个节点)
- 简化边界条件处理(如删除头节点时无需特殊处理)
2 双向链表扩展
class DoublyNode<E> { E data; DoublyNode<E> prev; // 前驱节点引用 DoublyNode<E> next; // 后继节点引用 public DoublyNode(E data) { this.data = data; this.prev = null; this.next = null; } }
典型应用:浏览器前进/后退历史记录、文本编辑器光标移动。

3 循环链表实现
// 在单链表基础上修改构造函数 public SinglyLinkedList() { head = new Node<>(null); head.next = head; // 形成自环 size = 0; }
特点:遍历时无需判断next == null,适合需要无限循环的场景。
常见错误与调试技巧
| 错误类型 | 现象 | 解决方案 |
|---|---|---|
| 空指针异常 | NullPointerException | 确保所有节点的next都有合理初始化,特别是最后一个节点的next=null |
| 索引越界 | IndexOutOfBoundsException | 在addAt/get/remove方法中增加索引范围校验(0 <= index <= size) |
| 内存泄漏 | JVM堆内存持续增长 | 确保删除节点时断开所有引用关系,避免垃圾回收失效 |
| 死循环 | 遍历时无法终止 | 检查终止条件是否为current != null(单链表)或current != dummyHead(带头节点) |
性能对比与选型建议
| 操作 | 单链表 | 双向链表 | ArrayDeque(JDK内置) | ArrayList(JDK内置) |
|---|---|---|---|---|
| 头部插入/删除 | O(1) | O(1) | O(1) | O(n) |
| 尾部插入/删除 | O(n) | O(1) | O(1) | O(n) |
| 随机访问 | O(n) | O(n) | O(n) | O(1) |
| 空间利用率 | 高 | 较低 | 高 | 高 |
选型原则:
- 频繁头部操作 → 单链表/双向链表
- 频繁尾部操作 → 双向链表(配合尾指针)
- 随机访问为主 → ArrayList/ArrayDeque
- 需要双向遍历 → 双向链表
完整代码示例(带头节点的单链表)
public class AdvancedLinkedList<E> { private Node<E> dummyHead; // 虚拟头节点 private int size; private static class Node<E> { E data; Node<E> next; public Node(E data) { this.data = data; } } public AdvancedLinkedList() { dummyHead = new Node<>(null); // 虚拟头节点不存数据 size = 0; } // 在指定位置插入元素(索引从0开始) public void addAt(int index, E element) { if (index < 0 || index > size) throw new IndexOutOfBoundsException(); Node<E> newNode = new Node<>(element); Node<E> prev = getNode(index 1); // 获取前驱节点 newNode.next = prev.next; prev.next = newNode; size++; } // 获取指定位置的节点(用于内部辅助) private Node<E> getNode(int index) { Node<E> current = dummyHead; for (int i = 0; i < index; i++) { current = current.next; } return current; } // 删除指定位置的元素 public E removeAt(int index) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException(); Node<E> prev = getNode(index 1); Node<E> toRemove = prev.next; E oldData = toRemove.data; prev.next = toRemove.next; // 跳过待删除节点 toRemove.next = null; // 清除引用防止内存泄漏 size--; return oldData; } // 遍历打印链表内容 public void printList() { Node<E> current = dummyHead.next; // 跳过虚拟头节点 while (current != null) { System.out.print(current.data + " -> "); current = current.next; } System.out.println("NULL"); } }
🧪 测试用例:
public static void main(String[] args) { AdvancedLinkedList<Integer> list = new AdvancedLinkedList<>(); list.addAt(0, 10); // [10] list.addAt(1, 20); // [10, 20] list.addAt(2, 30); // [10, 20, 30] list.printList(); // 输出: 10 -> 20 -> 30 -> NULL list.removeAt(1); // 删除20 → [10, 30] list.printList(); // 输出: 10 -> 30 -> NULL }
FAQs
Q1: Java已经有java.util.LinkedList,为什么还要手动实现?
A: Java标准库的LinkedList是基于双向链表实现的,虽然功能强大,但隐藏了底层细节,手动实现可以帮助理解链表的核心机制(如节点管理、指针操作),这对面试和算法学习至关重要,自定义链表可以针对特定需求进行优化(如只读链表、固定容量限制等)。
Q2: 如何处理链表中的重复元素?
A: 如果需要去重,可以在插入时遍历链表检查是否已存在相同元素,有两种策略:①允许重复但限制最大出现次数;②完全禁止重复,示例代码片段:
public boolean contains(E element) { Node<E> current = dummyHead.next; while (current != null) { if (element.equals(current.data)) return true; current = current.next; } return false; }
️ 注意:需重写equals()方法的对象才能正确判断相等
