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

Java 中 ArrayList 与 LinkedList 的深度对比:从原理到实战选择

在 Java 集合框架中,ArrayListLinkedList是两种常用的线性表实现,都继承自List接口,但内部结构和性能特性却有显著差异。本文将从数据结构、核心操作性能、内存占用等方面进行深度对比,并结合实际场景给出选择建议,帮助开发者在项目中做出更合理的选择。

一、底层数据结构差异

1. ArrayList 的数组实现

ArrayList基于动态数组实现,其内部维护一个Object[]数组来存储元素。当元素数量超过数组容量时,会自动触发扩容机制:

  • 初始容量默认为 10(JDK8)

  • 扩容时创建新数组(原容量的 1.5 倍),并将旧数组元素复制到新数组

  • 通过索引(index)直接访问元素,类似数组的随机访问特性

核心源码片段(简化):

public class ArrayList<E> {transient Object[] elementData; // 存储元素的数组private int size; // 当前元素数量// 添加元素时的扩容逻辑private void grow(int minCapacity) {int oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍扩容elementData = Arrays.copyOf(elementData, newCapacity);}
}

2. LinkedList 的双向链表实现

LinkedList基于双向链表实现,每个元素封装为Node节点,包含前驱指针(prev)、后继指针(next)和实际元素(item):

  • 无需预设容量,元素增减时只需调整指针指向

  • 不支持随机访问,需从表头 / 表尾开始遍历

  • 内部维护first(头节点)和last(尾节点)指针

核心源码片段(简化):

public class LinkedList<E> {transient Node<E> first; // 头节点transient Node<E> last;  // 尾节点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;}}
}

二、核心操作性能对比

1. 随机访问(get (int index))

  • ArrayList:通过索引直接访问数组元素,时间复杂度为O(1),性能优异

  • LinkedList:需从表头 / 表尾开始遍历至目标索引,时间复杂度为O(n),元素越多性能越差

测试验证(百万级元素):

// 随机访问性能测试
List<Integer> arrayList = new ArrayList<>();
List<Integer> linkedList = new LinkedList<>();// 初始化数据(省略)long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {arrayList.get(i * 100); // 随机访问
}
System.out.println("ArrayList耗时:" + (System.currentTimeMillis() - start) + "ms"); // 约1msstart = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {linkedList.get(i * 100); // 随机访问
}
System.out.println("LinkedList耗时:" + (System.currentTimeMillis() - start) + "ms"); // 约500ms

2. 新增元素(add ())

  • 尾部添加(add (E e))

    • ArrayList:若无需扩容,直接添加至数组尾部,O (1);若需扩容,涉及数组复制,O (n)
    • LinkedList:直接在尾节点后新增节点,O (1)(通过 last 指针直接操作)
  • 指定位置添加(add (int index, E e))

    • ArrayList:需移动 index 后的所有元素,O (n)
    • LinkedList:找到目标位置后只需调整指针,O (n)(查找耗时为主,插入本身 O (1))

3. 删除元素(remove ())

  • 按索引删除(remove (int index))

    • ArrayList:删除后需移动后续元素填补空位,O (n)
    • LinkedList:查找目标节点后调整指针,O (n)(同样是查找耗时)
  • 按元素删除(remove (Object o))

    • 两者均需先遍历找到元素(O (n)),区别在于后续操作:
      • ArrayList:删除后移动元素(O (n))
      • LinkedList:删除后调整指针(O (1))

4. 遍历性能

  • for 循环(通过索引)

    • ArrayList:性能优异(O (1) 访问)
    • LinkedList:性能极差(每次 get (index) 都是从头遍历)
  • 增强 for 循环(迭代器)

    • 两者性能接近,均通过Iterator遍历,时间复杂度 O (n)
    • LinkedList 的迭代器(ListItr)通过指针移动,效率更高

三、内存占用对比

  1. ArrayList

    • 内存连续分配,空间利用率高
    • 存在扩容预留空间(可能浪费部分内存)
    • 每个元素直接存储值,无额外指针开销
  2. LinkedList

    • 每个元素需额外存储 prev 和 next 指针(约 16 字节 / 节点,64 位 JVM)
    • 节点分散存储,可能导致更多的 GC 开销
    • 总内存占用通常高于 ArrayList(元素数量越多差距越大)

四、实战选择建议

根据上述特性,实际开发中可遵循以下原则

  1. 优先选择 ArrayList 的场景

    • 需要频繁随机访问元素(如通过索引查询)
    • 新增 / 删除操作主要在集合尾部
    • 元素数量固定或变化不大(可预设初始容量减少扩容)
  2. 优先选择 LinkedList 的场景

    • 频繁在集合中间位置进行插入 / 删除操作
    • 无需随机访问,仅需顺序遍历
    • 元素数量动态变化大,且无法预估容量
  3. 特殊注意事项

    • 当元素数量极小时(<100),两者性能差异可忽略
    • 频繁的 ArrayList 扩容会导致性能波动,建议通过new ArrayList(int initialCapacity)预设容量
    • 遍历 LinkedList 时,避免使用普通 for 循环(通过索引访问),推荐迭代器或增强 for 循环

五、总结

ArrayListLinkedList没有绝对的优劣,选择的核心在于操作场景与数据规模

  • 强调随机访问性能 → 选 ArrayList

  • 强调中间位置的增删操作 → 选 LinkedList

  • 大多数业务场景中,ArrayList 因更优的综合性能成为首选

理解两者的底层实现原理,才能在面对具体问题时做出合理选择,写出更高效的 Java 代码。


文章转载自:

http://TjehxYth.qfwfj.cn
http://vwDgXpnw.qfwfj.cn
http://JHiWYYRb.qfwfj.cn
http://WQmVBWkW.qfwfj.cn
http://010RhQRc.qfwfj.cn
http://UKTWh3AG.qfwfj.cn
http://YRB0B9jH.qfwfj.cn
http://VSUMqQOV.qfwfj.cn
http://3zrfsWd6.qfwfj.cn
http://B1BJ78HY.qfwfj.cn
http://bbe0UU8e.qfwfj.cn
http://fP5SKRK0.qfwfj.cn
http://ilnLAopJ.qfwfj.cn
http://avlCka7t.qfwfj.cn
http://E4R0I2AI.qfwfj.cn
http://8xs0JYx0.qfwfj.cn
http://2ALWQqXS.qfwfj.cn
http://2WbjLvzS.qfwfj.cn
http://4L0PO9FA.qfwfj.cn
http://9cmWO98m.qfwfj.cn
http://ogR6pNhV.qfwfj.cn
http://HBXUYoCv.qfwfj.cn
http://l7Aupyz7.qfwfj.cn
http://kJGQKiZ9.qfwfj.cn
http://2NoY0x8D.qfwfj.cn
http://bFJQJyHZ.qfwfj.cn
http://mzeJXKUw.qfwfj.cn
http://msd9qpiQ.qfwfj.cn
http://rgmLpCra.qfwfj.cn
http://5R2pomkj.qfwfj.cn
http://www.dtcms.com/a/387805.html

相关文章:

  • 向量检索服务 DashVector产品功能
  • Spring-Cloud-Alibaba:2023.0.1.X引起的dashscope-sdk-java依赖冲突问题
  • vue 知识点
  • 深入理解 Linux 进程调度:从策略到实现的全方位解析
  • 【技术架构】从单机到微服务:Java 后端架构演进与技术选型核心方案
  • Java异常报错: java.io.IOException: Broken pipe
  • [Linux]学习笔记系列 -- lib/kobject.c 内核对象(Kernel Object) 设备模型的核心基石
  • 专题:Python实现贝叶斯线性回归与MCMC采样数据可视化分析2实例|附代码数据
  • IEEE 802.1X和**IEEE 802.11之间的关联和作用
  • 【Linux】【底层解析向】Linux Shell 核心功能拆解:环境变量不生效原因 + $?/echo/alias 底层逻辑
  • UV紫外卤素灯太阳光模拟器的原理
  • RAG简单构建(ollama+uv+deepseek)
  • 告别冰冷AI音!B站开源IndexTTS2模型,零样本克隆+情感解耦,玩法超多!
  • pytorch中.pt和.pth文件区别
  • 目标计数(3)Object Counting: You Only Need to Look at One
  • 拖拽移动并监听点击事件
  • Hibernate 和 MyBatis差异分析
  • RAG 核心技术深度剖析:架构设计与性能优化实战指南
  • Java全栈学习笔记36
  • python 任务管理器
  • AI 驱动智能驾驶:L4 级技术落地瓶颈、车企博弈与用户信任构建
  • VS Code和Cursor扩展主机在过去5分钟内意外终止了3次问题解决方案
  • 【TestCenter】创建DHCP Server和DHCP Client
  • 内存泄漏系列专题分析之三十五:开机内存性能优化之一:Camx进程启动提前加载so库
  • 知微传感Dkam系列3D相机SDK例程篇:CSharp设置相机工作模式
  • 《华为基本法》 —— 企业发展的导航仪
  • devops平台建设-总体设计文档
  • 大数据七大业务架构横向比对分析
  • C#面试题及详细答案120道(21-30)-- 集合与泛型
  • 如何对AI代理的决策进行审计和监督?