跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava算法

Java 双向链表实现与 LinkedList 源码解析

详细讲解了 Java 中双向链表的数据结构原理及模拟实现,包括节点设计、头插尾插、指定位置插入删除等操作。随后深入分析了 JDK 中 LinkedList 类的底层机制、构造方法及常用 API。最后对比了 ArrayList 与 LinkedList 在存储结构、访问效率及应用场景上的差异,帮助开发者根据实际需求选择合适的数据结构。

二进制发布于 2026/2/6更新于 2026/10/2487 浏览
Java 双向链表实现与 LinkedList 源码解析

一、双向链表

双向链表与单向链表相比,多了一个前驱 prev,解决了单向链表很多缺点:单向链表的每个节点仅存储后继节点地址,这导致其在实际应用中存在明显局限:

遍历方向单一:只能从表头(head)向表尾(tail)遍历,无法反向查找。 删除操作低效:若要删除指定节点,需先从头遍历找到其前驱节点,时间复杂度为 O(n)。 表尾操作不便:无法直接获取表尾节点的前驱,若需在表尾插入后修改前驱指针,需重新遍历。

双向链表通过在节点中增加前驱节点地址域,解决了以上问题,所以在实际开发中,使用更多的是双向链表。

二、双向链表的模拟实现

1. 节点类

val 数据,next 后继,prev 前驱,双向链表的前驱节点,可以找到前一个节点,为了方便从后往前遍历,我们使用 last 标记最后一个节点。

static class ListNode {
    public int val;
    public ListNode next;
    public ListNode prev;

    public ListNode(int val) {
        this.val = val;
    }
}
public ListNode head;
public ListNode last;

2. 头插法

先创建一个新节点 node,如果链表为空,head 和 last 都指向新节点,当链表不为空时,让新节点的 next 指向头节点(先绑定后面),然后将头节点的前驱 prev 赋值为新节点的地址,最后让 head 指向新的节点。

public void addFirst(int data) {
    ListNode node = new ListNode(data);
    if (head == null) {
        head = node;
        last = node;
    } else {
        node.next = head;
        head.prev = node;
        head = node;
    }
}

3. 尾插法

跟头插法一样,创建一个节点,判断链表是否为空,为空就让 head 和 last 都指向新节点,当链表不为空时,让最后一个节点的 next 指向新的节点(先绑定),然后让新节点的前驱指向前一个节点的地址,最后让 last 指向新节点。

public void addLast( data) {
        (data);
     (head == ) {
        head = node;
        last = node;
    }  {
        last.next = node;
        node.prev = last;
        last = node;
    }
}
int
ListNode
node
=
new
ListNode
if
null
else

4. 指定位置插入

双向链表可以找到前一个节点,所以可以让 cur 直接走到想要插入的位置,先判断位置是否合法,然后开始插入,先绑定后面让新节点的 next 指向 cur 节点的地址,然后让前一个节点的 next 指向新的节点的地址,在将新节点的前驱改为 cur 的前驱,cur 的前驱改为新节点。

public void addIndex(int index, int data) {
    // 检查 index 的合法性
    checkIndex(index);
    // 特殊位置处理
    if (index == 0) {
        addFirst(data);
        return;
    }
    if (index == size()) {
        addLast(data);
        return;
    }
    // 指定插入
    ListNode cur = searchIndex(index);
    ListNode node = new ListNode(data);
    node.next = cur;
    cur.prev.next = node;
    node.prev = cur.prev;
    cur.prev = node;
}

// 寻找位置
private ListNode searchIndex(int index) {
    ListNode cur = head;
    while (index != 0) {
        cur = cur.next;
        index--;
    }
    return cur;
}

// 自定义异常
private void checkIndex(int index) {
    if (index < 0 || index > size()) {
        throw new IllegalArgumentException("index 位置不合法:" + index);
    }
}

5. 删除值为 key 的节点

先遍历,找到所要删除的那个节点,当 cur 走到要删除的节点时,常规操作:cur.next.prev = cur.prev; cur.prev.next = cur.next;但还有其他情况:比如要删除的是头节点或者尾节点,只要一个节点的情况,具体操作看下面的代码详解。

public void remove(int key) {
    ListNode cur = head;
    while (cur != null) {
        if (cur.val == key) {
            // 开始删除
            if (cur == head) {
                // 删除的时头节点
                head = head.next;
                // 如果只有一个节点,head 为空,它的前驱就会空指针异常,所以判断一下
                if (head != null) {
                    head.prev = null;
                }
            } else {
                // 不是头节点
                if (cur == last) {
                    // 删除的是尾节点
                    cur.prev.next = cur.next;
                    last = last.prev;
                } else {
                    // 正常情况:中间节点
                    cur.next.prev = cur.prev;
                    cur.prev.next = cur.next;
                }
            }
            return;
        }
        cur = cur.next;
    }
}

6. 删除所有值为 key 的节点

只要将上面的代码中 return 去掉,就能删除所有值为 key 的节点,删完一个后不返回,继续遍历,直到全部删除。

public void removeAllKey(int key) {
    ListNode cur = head;
    while (cur != null) {
        if (cur.val == key) {
            // 开始删除
            if (cur == head) {
                // 删除的时头节点
                head = head.next;
                // 如果只有一个节点,head 为空,它的前驱就会空指针异常,所以判断一下
                if (head != null) {
                    head.prev = null;
                }
            } else {
                // 不是头节点
                if (cur == last) {
                    // 删除的是尾节点
                    cur.prev.next = cur.next;
                    last = last.prev;
                } else {
                    // 正常情况:中间节点
                    cur.next.prev = cur.prev;
                    cur.prev.next = cur.next;
                }
            }
        }
        cur = cur.next;
    }
}

7. 清空链表

【方法一:】直接将头和尾置空

public void clear() {
    head = last = null;
}

【方法二:】将一个一个的节点置空

public void clear() {
    ListNode cur = head;
    while (cur != null) {
        ListNode curN = cur.next;
        cur.prev = null;
        cur.next = null;
        cur = curN;
    }
    head = null;
    last = null;
}

三、LinkedList

1. LinkedList 的概念

LinkedList 使用双向链表结构,实现了 List 接口。在任意位置插入和删除元素时效率较高,时间复杂度为 O(1)。

2. 构造方法

方法说明表格
方法签名描述
LinkedList()无参构造方法,创建一个空链表。
public LinkedList(Collection<? extends E> c)使用其他集合容器中的元素构造 List
public static void main(String[] args) {
    LinkedList<Integer> list = new LinkedList<>();
    List<Integer> list1 = new LinkedList<>();
    List<Integer> list2 = new ArrayList<>();
    list.add(1); // 默认尾插
    list.add(11);
    list.add(12);
    list.add(13);
    System.out.println(list);
}

3. LinkedList 常用方法

方法解释
boolean add(E e)在列表尾部插入元素 e
void add(int index, E element)将元素 e 插入到指定 index 位置
boolean addAll(Collection<? extends E> c)将集合 c 中所有元素插入到列表尾部
E remove(int index)删除并返回 index 位置的元素
boolean remove(Object o)删除列表中首次遇到的指定对象 o
E get(int index)获取 index 位置的元素
E set(int index, E element)将 index 位置的元素替换为 element 并返回旧值
void clear()清空列表中的所有元素
boolean contains(Object o)判断列表中是否包含对象 o
int indexOf(Object o)返回第一个 o 对象的下标,未找到返回 -1
int lastIndexOf(Object o)返回最后一个 o 对象的下标,未找到返回 -1
List<E> subList(int fromIndex, int toIndex)截取从 fromIndex 到 toIndex 的子列表

4. LinkedList 遍历

4.1 直接打印
System.out.println(list);
4.2 for-each 遍历
for (Integer x : list) {
    System.out.println(x + " ");
}
4.3 迭代器遍历
// 迭代器遍历
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
    System.out.println(it.next() + " ");
}
// 迭代器遍历:专门遍历 List
ListIterator<Integer> listIterator = list.listIterator();
// 可以给参数,从 index 开始打印
while (listIterator.hasNext()) {
    System.out.println(listIterator.next() + " ");
}
// 反向迭代器
ListIterator<Integer> listIterator1 = list.listIterator();
// 可以给参数,从 index 开始打印
while (listIterator.hasPrevious()) {
    System.out.println(listIterator.next() + " ");
}

5. ArrayList 和 LinkList 的区别

不同点ArrayListLinkedList
存储空间物理上连续逻辑上连续,物理上不连续
随机访问支持 O(1) 时间复杂度不支持,需遍历 O(N) 时间复杂度
头插操作需搬移元素,效率低 O(N)修改引用指向,效率高 O(1)
插入扩容空间不足时需扩容无容量概念,动态增加节点
应用场景元素高效存储 + 频繁随机访问频繁在任意位置插入/删除

四、总结

本文围绕双向链表和 LinkedList 展开了系统讲解。

双向链表凭借'每个节点保存前驱和后继引用'的结构,在插入、删除操作上有着 O(1) 的时间复杂度优势。LinkedList 作为双向链表的 Java 实现,不仅提供了如 addFirst、removeLast 等丰富方法,支持迭代器、增强 for 等多种遍历方式,还能充当队列、栈使用。和 ArrayList 相比,LinkedList 适合频繁进行插入删除的场景,ArrayList 则更适合元素存储和随机访问的场景。掌握这些知识,能让你在面对不同业务场景时,合理选择数据结构,写出高效且易维护的代码。

目录

  1. 一、双向链表
  2. 二、双向链表的模拟实现
  3. 1. 节点类
  4. 2. 头插法
  5. 3. 尾插法
  6. 4. 指定位置插入
  7. 5. 删除值为 key 的节点
  8. 6. 删除所有值为 key 的节点
  9. 7. 清空链表
  10. 三、LinkedList
  11. 1. LinkedList 的概念
  12. 2. 构造方法
  13. 方法说明表格
  14. 3. LinkedList 常用方法
  15. 4. LinkedList 遍历
  16. 4.1 直接打印
  17. 4.2 for-each 遍历
  18. 4.3 迭代器遍历
  19. 5. ArrayList 和 LinkList 的区别
  20. 四、总结

更多推荐文章

查看全部
  • 通义万相 2.1 结合云算力实现 AIGC 高效创作
  • 智能协同云图库:Redis+Caffeine 多级缓存与图片全链路优化实战
  • AI 绘画技术演进:从 DALL·E 系列到 Stable Diffusion 解析
  • Java 直播商城平台架构规划与常见营销模式解析
  • Windows 家用电脑部署 Gemma3 大模型:Ollama+Open WebUI 搭建 AI 聊天室
  • Claude Code 平替方案:OpenCode + GitHub Copilot
  • Slack 机器人集成:基于 InstructPix2Pix 的快速修图协作方案
  • MySQL 内置函数实战指南:日期、字符串与数学运算
  • 深入解析 LRU 与 LFU 缓存算法原理及实现
  • 深入理解大语言模型:原理、训练与未来趋势
  • GitHub MCP 服务配置与 Cursor 集成实战
  • 2025 年量子计算算法发展趋势预测
  • Linux Mint 系统卸载 OpenJDK 并安装 Oracle JDK
  • xiaozhi-esp32 AI 语音机器人开发指南
  • 图数据结构详解:存储结构、遍历与核心算法
  • AI 深度早报:GTC 开幕,AI Agent 平台与具身世界模型双线引爆
  • 2025 AIGC 最具影响力 AI 应用开发平台公布
  • 前端团队协作最佳实践
  • 考公压力大是否该放弃?程序员职业路径的理性思考
  • GitHub 汉化插件安装指南:三步实现界面中文化

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online