当前位置: 首页 > news >正文

Java数据结构之LinkedList

Java 中的 LinkedList —— 这是一个基于双向链表的集合类,与 ArrayList 的数组结构形成鲜明对比。


一、LinkedList 的底层结构

1. 底层实现:双向链表

  • LinkedList 是基于 双向链表(Doubly Linked List) 实现的,每个节点包含:
    • 数据(element
    • 前驱节点指针(prev
    • 后继节点指针(next
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;}
}
  • LinkedList 维护两个头尾指针:
    • first:指向链表的第一个节点
    • last:指向链表的最后一个节点

二、LinkedList 的核心特性

特性说明
有序元素按插入顺序存储
可重复允许重复元素
动态大小插入/删除时自动调整链表长度
线程不安全多线程下需手动同步
允许 null可以存储 null 值
双向链表支持正向和反向遍历

 三、时间复杂度对比(与 ArrayList 对比)

操作LinkedListArrayList
get(index)O(n)O(1) ⚡️
add(E e)O(1)(尾部)均摊 O(1)(偶尔扩容 O(n))
add(index, E e)O(n)(需定位)O(n)(需移动元素)
remove(index)O(n)(需定位)O(n)(需移动元素)
contains(E e)O(n)O(n)
内存占用高(每个节点额外存储 prev/next)低(仅存储元素)

一句话:

  • 随机访问ArrayList 更快
  • 中间插入/删除LinkedList 更快
  • 尾部操作:两者接近(LinkedList 无扩容开销)

四、LinkedList vs ArrayList 的核心区别

对比项LinkedListArrayList
底层结构双向链表动态数组
随机访问O(n)O(1)
插入/删除(中间)O(1)(已知位置)O(n)(需移动元素)
内存占用高(每个节点有 prev/next 指针)低(仅存储元素)
适用场景频繁中间插入/删除频繁随机访问、尾部操作
                                                                线程都不安全
是否支持 RandomAccess支持(但慢)支持(且快)

如果业务中主要是频繁在中间插入或删除,且不常随机访问,LinkedList 更合适;如果是频繁按索引查询或尾部操作ArrayList 性能更好。


五、LinkedList 的常见操作源码解析

1. 添加元素(尾部)

public boolean add(E e) {linkLast(e); // 直接添加到尾部return true;
}void linkLast(E e) {final Node<E> l = last;final Node<E> newNode = new Node<>(l, e, null);last = newNode;if (l == null) {first = newNode; // 如果链表为空} else {l.next = newNode;}size++;modCount++;
}
  • 时间复杂度:O(1)

2. 插入元素(指定位置)

public void add(int index, E element) {checkPositionIndex(index);if (index == size) {linkLast(element);} else {Node<E> succ = node(index); // 找到指定位置的节点Node<E> pred = succ.prev;Node<E> newNode = new Node<>(pred, element, succ);succ.prev = newNode;if (pred == null) {first = newNode;} else {pred.next = newNode;}size++;modCount++;}
}
  • 时间复杂度:O(n)(需定位节点)

六、LinkedList 的线程安全问题

1. 线程不安全

  • LinkedList 不是线程安全的。多线程环境下,如果多个线程同时修改(如 add/remove),可能导致数据不一致或异常。

2. 如何保证线程安全?

方法说明使用场景
Collections.synchronizedList()包装成同步列表通用
CopyOnWriteArrayList写时复制,读不加锁读多写少
手动加锁使用 synchronized 或 ReentrantLock灵活控制
// 方式1:同步包装
List<String> syncList = Collections.synchronizedList(new LinkedList<>());// 方式2:使用 CopyOnWriteArrayList
List<String> cowList = new CopyOnWriteArrayList<>();

对比

  • synchronizedList:所有操作加锁,性能较低。
  • CopyOnWriteArrayList:写操作复制整个数组,开销大,但读操作无锁,适合“读多写少”场景(如监听器列表)。

七、LinkedList 的 fail-fast 机制

  • LinkedList 的迭代器同样基于 modCount 实现 fail-fast 机制。
  • 原理
    • 迭代器创建时保存 expectedModCount = modCount
    • 每次 next() 前检查 modCount == expectedModCount,不一致则抛出 ConcurrentModificationException
final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();
}
  • 单线程下,边遍历边修改也会触发异常。
  • 正确删除方式:使用 Iterator.remove(),它会同步更新 expectedModCount

八、LinkedList 的一些理论问题

1. 为什么 LinkedList 的随机访问效率低?

因为 LinkedList 是基于链表实现的,访问第 n 个元素需要从头节点或尾节点遍历到目标位置,时间复杂度为 O(n)

// LinkedList 的 get(index) 实现
public E get(int index) {checkElementIndex(index);return node(index).item;
}Node<E> node(int index) {// 如果 index 在前半部分,从头开始遍历// 否则从尾开始遍历if (index < (size >> 1)) {Node<E> x = first;for (int i = 0; i < index; i++)x = x.next;return x;} else {Node<E> x = last;for (int i = size - 1; i > index; i--)x = x.prev;return x;}
}

2. LinkedList 和 ArrayList 的适用场景?

场景推荐使用
频繁随机访问ArrayList
频繁中间插入/删除LinkedList
尾部频繁操作ArrayList(无扩容开销)
读多写少CopyOnWriteArrayList

3. LinkedList 的内存占用为什么比 ArrayList 高?

LinkedList 的每个节点除了存储元素外,还需要存储 prevnext 指针,导致内存开销比 ArrayList 大。


4. 如何高效删除 LinkedList 中的所有值为 100 的元素?

推荐方式

// 使用迭代器(避免并发修改异常)
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {if (it.next() == 100) {it.remove(); // ✅ 安全删除}
}// 或使用 removeIf(Java 8+)
list.removeIf(x -> x == 100);

 九、LinkedList 的优缺点总结

优点缺点
插入/删除效率高(O(1),已知位置)随机访问效率低(O(n))
尾部添加无需扩容内存占用高(每个节点有指针)
支持双向遍历(ListIterator不适合频繁随机访问的场景

十、实际应用

  1. 何时选择 LinkedList

    • 需要频繁在头部或中间插入/删除元素(如任务队列、历史记录)。
    • 不需要频繁随机访问元素。
  2. 何时避免 LinkedList

    • 需要频繁按索引访问元素(如日志查询、缓存)。
    • 内存敏感场景(链表节点指针占用额外内存)。

 十一、得到的技巧

题目:如何高效合并两个有序的 LinkedList?

 参考

// 使用双指针合并两个有序链表
public static LinkedList<Integer> mergeTwoSortedLists(LinkedList<Integer> l1, LinkedList<Integer> l2) {LinkedList<Integer> result = new LinkedList<>();Node<Integer> p1 = l1.getFirstNode(), p2 = l2.getFirstNode();while (p1 != null && p2 != null) {if (p1.item <= p2.item) {result.add(p1.item);p1 = p1.next;} else {result.add(p2.item);p2 = p2.next;}}// 添加剩余元素while (p1 != null) {result.add(p1.item);p1 = p1.next;}while (p2 != null) {result.add(p2.item);p2 = p2.next;}return result;
}

解释

  • 时间复杂度:O(m + n)
  • 空间复杂度:O(m + n)

最后总结:LinkedList 核心要点

考点答案
底层结构双向链表
随机访问性能O(n)(需遍历)
插入/删除性能O(1)(已知位置)
内存占用高(每个节点有指针)
与 ArrayList 的区别链表 vs 数组,插入删除 vs 随机访问
线程安全否,需用 synchronizedList 或 CopyOnWriteArrayList
fail-fast 机制与 ArrayList 一致,迭代时修改会抛异常
适用场景频繁中间插入/删除,不适合随机访问
http://www.dtcms.com/a/328592.html

相关文章:

  • 电子电路原理学习笔记---第5章特殊用途二极管---第2天
  • 基于51单片机RFID智能门禁系统红外人流量计数统计
  • -bash: ./restart.sh: /bin/bash^M: 坏的解释器: 没有那个文件或目录
  • MySQL 从入门到精通:基础概念与操作指南
  • Sklearn 机器学习 异常值检测 局部异常因子算法LOF
  • SQL连接操作全解析:从入门到精通
  • 某跨国金融机构法律法规自动文本摘要(ATS/文本大意提取)功能规划
  • 嵌入式第二十六天(文件IO相关操作)
  • [Robotics_py] docs | 机器人状态/位姿 | 环境表示_栅格地图
  • 准则 :用“检测到什么”的方式来编写需求条件
  • Python 异常捕获
  • 为什么我换了项目管理软件?
  • 如何在 Odoo 18 管理产品文档手册
  • Redis面试题及详细答案100道(16-32) --- 数据类型事务管道篇
  • 第23章,景深:技术综述
  • 软件测试之功能测试
  • 嵌入式系统学习Day17(文件编程)
  • (树形 dp、数学)AT_dp_v Subtree 题解
  • 架构设计:设计原则
  • 第十一节:加载外部模型:GLTF/OBJ格式解析
  • [MySQL数据库] 数据库简介
  • 【虚拟机】VMwareWorkstation17Pro安装步骤
  • Tricentis Tosca 2025.1 LTS 系统要求
  • 华为OD最新机试真题-国际移动用户识别码(IMSI)匹配-(C卷)
  • Terminal Security: Risks, Detection, and Defense Strategies
  • [激光原理与应用-255]:理论 - 几何光学 - CCD成像过程
  • 维文识别技术:将印刷体或手写体的维文文本转化为计算机可处理的数字信息
  • 网络协议组成要素
  • 网络协议——HTTP协议
  • Java锁机制全景解析:从基础到高级的并发控制艺术