当前位置: 首页 > 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.lryc.cn/news/618777.html

相关文章:

  • 【开发环境下浏览器前后端Cookie跨域问题】
  • 视频安全预警系统的应用价值
  • vue3用quill富文本赋值后回退键删除报错
  • 可以免费使用的数字人API
  • 亚马逊POST退场后的增长突围:关联与交叉销售的全链路策略重构
  • 一维数组的创建、初始化与使用指南
  • 详解k6中的核心概念——场景(Scenarios)
  • Spring面试宝典
  • Pytest项目_day13(usefixture方法、params、ids)
  • Linux系统管理利器lsof命令详解与实战应用
  • 杰理手表-增加提示音-提示音音量调整--使用提示音
  • kafka 消费者组的概念是什么?它是如何实现消息的点对点和发布/订阅模式?
  • 无人机航拍数据集|第14期 无人机水体污染目标检测YOLO数据集3000张yolov11/yolov8/yolov5可训练
  • Linux中Https配置与私有CA部署指南
  • 股指期货基本术语是什么?
  • 云计算分类与主流产品
  • Neo4j Cypher语句
  • 设置默认的pip下载清华源(国内镜像源)和pip使用清华源
  • day49 力扣42. 接雨水 力扣84.柱状图中最大的矩形
  • 零基础数据结构与算法——第七章:算法实践与工程应用-性能分析与瓶颈
  • 全面解析远程桌面:功能实现、性能优化与安全防护全攻略
  • 北京-4年功能测试2年空窗-报培训班学测开-第七十四天-线下面试-聊的很满意但可能有风险-等信吧
  • 第十篇:3D模型性能优化:从入门到实践
  • 【DL】Deep Learning base
  • CASS11三维坡度着色显示
  • PR新建项目
  • ARM芯片架构之CoreSight SoC-400 组件介绍
  • windows单机单卡+CIFAR-10数据集+Docker模拟训练
  • 自建知识库,向量数据库 体系建设(一)之BERT 与.NET 4.5.2 的兼容困境:技术代差下的支持壁垒
  • 【数据分享】2018-2024年中国10米分辨率春小麦和冬小麦分布栅格数据