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

LinkedList 底层实现与 ArrayList 对比分析

目录

一、LinkedList 的底层数据结构:双向链表

1. 节点(Node)的结构

2. 双向链表的整体结构

二、LinkedList 的核心特性

1. 增删效率高:无需移动元素,仅修改指针

示例 1:尾部添加元素(add(E e))

示例 2:指定位置删除元素(remove(int index))

2. 无需扩容:内存利用率更高

3. 实现多接口:支持队列 / 栈操作

4. 线程不安全:与 ArrayList 一致

三、LinkedList vs ArrayList:核心差异对比

四、LinkedList 的使用

五、总结

一、LinkedList 的底层数据结构:双向链表

LinkedList的底层是双向链表(Doubly Linked List),这是它与ArrayList(动态数组)最本质的区别。链表结构不依赖连续内存空间,而是通过 “节点间的引用” 串联元素,每个节点(Node)包含三个部分:

1. 节点(Node)的结构

LinkedList内部定义了一个私有的Node类,作为链表的基本单元,源码如下:

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;}
}

2. 双向链表的整体结构

LinkedList通过两个指针(firstlast)维护整个链表的首尾节点,无需像数组那样记录 “容量”,只需跟踪实际元素个数(size):

public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable {transient Node<E> first; // 指向链表的头节点transient Node<E> last;  // 指向链表的尾节点transient int size = 0;  // 实际元素个数
}

双向链表的结构特点:

  • 每个节点都能通过prevnext找到前后节点,支持 “双向遍历”;
  • 链表的首尾节点特殊:头节点的prevnull,尾节点的nextnull
  • 新增 / 删除节点时,只需修改前后节点的引用(指针),无需移动大量元素。

二、LinkedList 的核心特性

基于双向链表的结构,LinkedList呈现出与ArrayList完全不同的特性,核心优势集中在 “增删操作” 上:

1. 增删效率高:无需移动元素,仅修改指针

无论是在链表的 “头部、尾部还是中间位置” 增删元素,LinkedList只需修改对应节点的prevnext引用,时间复杂度为O(1)(前提是已知目标节点位置)。

示例 1:尾部添加元素(add(E e)

直接通过last指针找到尾节点,新建节点并修改引用:

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); // 新建节点,前驱为l,后继为nulllast = newNode; // 新节点成为新的尾节点if (l == null) // 若原链表为空,新节点同时也是头节点first = newNode;elsel.next = newNode; // 原尾节点的后继指向新节点size++;
}
示例 2:指定位置删除元素(remove(int index)

先通过node(index)方法找到目标节点,再修改前后节点的引用:

public E remove(int index) {checkElementIndex(index); // 检查索引合法性return unlink(node(index)); // 找到节点并删除
}// 找到指定索引的节点(优化:根据索引位置选择从头或从尾遍历,提升效率)
Node<E> node(int 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;}
}// 删除目标节点:修改前后节点的引用,断开目标节点的连接
E unlink(Node<E> x) {final E element = x.item;final Node<E> next = x.next; // 目标节点的后继final Node<E> prev = x.prev; // 目标节点的前驱if (prev == null) { // 目标节点是头节点first = next;} else {prev.next = next; // 前驱节点的后继指向目标节点的后继x.prev = null; // 断开目标节点的前驱引用}if (next == null) { // 目标节点是尾节点last = prev;} else {next.prev = prev; // 后继节点的前驱指向目标节点的前驱x.next = null; // 断开目标节点的后继引用}x.item = null; // 帮助GC回收size--;return element;
}

2. 无需扩容:内存利用率更高

LinkedList的元素存储依赖节点,每个节点按需创建(添加元素时新建节点,删除元素时回收节点),无需像ArrayList那样 “提前预留容量” 或 “扩容时复制数组”,内存利用率更高,不会产生 “空闲容量浪费”。

3. 实现多接口:支持队列 / 栈操作

LinkedList不仅实现了List接口,还实现了Deque接口(双端队列),因此可以作为队列(FIFO) 或栈(LIFO) 使用,提供了丰富的操作方法:

  • 队列操作:offer()(尾部入队)、poll()(头部出队)、peek()(获取头部元素);
  • 栈操作:push()(头部入栈)、pop()(头部出栈)、peek()(获取栈顶元素)。

4. 线程不安全:与 ArrayList 一致

ArrayList一样,LinkedList的所有方法都没有加锁,多线程环境下同时修改会导致数据不一致(如节点引用错乱)。若需线程安全,可使用Collections.synchronizedList(new LinkedList<>())CopyOnWriteArrayList(后者更适合读多写少场景)。

三、LinkedList vs ArrayList:核心差异对比

对比维度LinkedList(双向链表)ArrayList(动态数组)
底层结构双向链表(节点 + 指针)动态数组(Object [])
随机访问(get (index))效率低(需遍历链表,O (n))效率高(索引直接访问,O (1))
增删操作效率高(仅修改指针,O (1),已知节点时)效率低(需移动元素,O (n),非尾部时)
尾部增删(add/removeLast)效率高(O (1))效率高(O (1),无扩容时);扩容时 O (n)
内存占用内存开销大(每个节点存 prev/next 引用)内存开销小(仅存元素,可能有空闲容量)
扩容机制无需扩容(按需创建节点)自动扩容(默认 1.5 倍,需复制数组)
遍历方式推荐迭代器 / 增强 for(避免随机访问)推荐随机访问 / 增强 for / 迭代器
适用场景频繁增删(尤其是中间位置)、队列 / 栈频繁查询(随机访问)、少增删

四、LinkedList 的使用

  1. 优先用于 “频繁增删” 场景:如任务队列、消息队列等需要频繁添加 / 删除元素的场景,避免在 “频繁查询” 场景中使用(如商品列表展示,需频繁通过索引获取元素)。

  2. 避免随机访问(get (index)):若需频繁通过索引获取元素,果断改用ArrayList。LinkedList 的get(index)方法虽做了优化(根据索引位置选择从头或从尾遍历),但仍需 O (n) 时间,远慢于 ArrayList 的 O (1)。

  3. 批量添加元素时,无需手动优化:与 ArrayList 不同,LinkedList 添加元素时无需提前 “预留容量”,直接循环add()即可,不会产生扩容开销。

  4. 遍历推荐用迭代器或增强 for:LinkedList 的迭代器(Iterator)是 “fail-fast” 机制(遍历中修改会抛ConcurrentModificationException),且迭代器遍历比普通 for 循环(频繁调用get(index))效率高得多。

五、总结

LinkedList 的设计核心是 “双向链表”,它用 “牺牲随机访问效率” 换取 “高效的增删操作”,完美弥补了 ArrayList 在 “频繁增删” 场景中的不足。在实际开发中,选择两者的核心依据是 “业务操作的侧重点”:

  • 若以 “查询” 为主,偶尔增删(且多在尾部)→ 选 ArrayList;
  • 若以 “增删” 为主(尤其是中间位置),少查询 → 选 LinkedList。


文章转载自:

http://uOUbi3Zi.mxnfh.cn
http://fCeSqV9C.mxnfh.cn
http://ZPxeNAUg.mxnfh.cn
http://GJvZnDyZ.mxnfh.cn
http://04NdPure.mxnfh.cn
http://kBWNPA4V.mxnfh.cn
http://rGCiUxxV.mxnfh.cn
http://rcHxj7z0.mxnfh.cn
http://QUGEDA2x.mxnfh.cn
http://JGO1f46t.mxnfh.cn
http://GmY23KEq.mxnfh.cn
http://slrZhTjW.mxnfh.cn
http://MF2p1Qh9.mxnfh.cn
http://oxuJOZIw.mxnfh.cn
http://RQVG823c.mxnfh.cn
http://yXNop3Dw.mxnfh.cn
http://SG4EBbbv.mxnfh.cn
http://9cs8AkfY.mxnfh.cn
http://Ekr5d79X.mxnfh.cn
http://V50VSnxr.mxnfh.cn
http://nBbaayMC.mxnfh.cn
http://xVhVNTvb.mxnfh.cn
http://64txFUXh.mxnfh.cn
http://6Lp6BJlT.mxnfh.cn
http://Pom5Yvkc.mxnfh.cn
http://IzA31IWx.mxnfh.cn
http://15r6l6FK.mxnfh.cn
http://LESpOCFH.mxnfh.cn
http://7IhgG27M.mxnfh.cn
http://iE3moXPz.mxnfh.cn
http://www.dtcms.com/a/386429.html

相关文章:

  • 滚珠花键在半导体制造设备中承担怎样的核心功能?
  • 服装制造企业痛点解决方案:EFISH-SBC-RK3588 柔性化吊挂调度方案
  • 10cm钢板矫平机:工业制造中的“整形医生”
  • html表单登录模式代码
  • QUIC 协议域名封堵:核心原理、关键技术与实现路径(C/C++代码实现)
  • 8 基于机器学习进行遥感影像的地物分类-以随机森林为例
  • Qt读写SQLite示例
  • Jmeter性能测试之阶梯式场景、波浪式场景、服务器监控
  • 黄昏时刻复古胶片风格人像风光摄影后期Lr调色教程,手机滤镜PS+Lightroom预设下载!
  • Django ORM多对多关系实战指南
  • 【从零开始java学习|第十七篇】面向对象进阶
  • Three.js 开发实战教程(一):环境搭建与第一个 3D 场景
  • 旅游小程序的功能优势
  • LeetCode:7.接雨水
  • Android 安卓 问题解决记录 腾讯IM和厂商离线推送问题 点击离线推送无法唤醒APP启动页但某些Service服务和Application被启动
  • 动态规划解决系列子序列问题
  • SCADE One vs Scade 6 - 标量积建模比较
  • Next.js 身份验证与授权:使用 NextAuth.js 保护你的应用
  • Spring MVC 的案例小练习
  • 贪心算法与动态规划
  • 香港期权市场的主要参与者有哪些?
  • 系统中间件与云虚拟化-serverless-基于阿里云函数计算的简单邮件发送服务设计与体验
  • 【LLM】GPT-OSS架构变化详解
  • 【开题答辩全过程】以 “寄情绿苑”绿色殡葬服务小程序的设计和实现为例,包含答辩的问题和答案
  • 容器化部署之dockerfile07
  • 一篇读懂Pormise!!【前端ES6】
  • spring-kafka的消息过滤器RecordFilterStrategy
  • gin中sse流式服务
  • 论文笔记(九十一)GWM: Towards Scalable Gaussian World Models for Robotic Manipulation
  • Simulink(MATLAB)与 LabVIEW应用对比