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

Java集合框架深度解析:LinkedList vs ArrayList 的对决

前言

在 Java 集合框架中,ArrayList​ 和 LinkedList​ 是两种最常用的 List​ 实现类,它们都用于存储和操作有序集合。尽管它们都实现了 List​ 接口,但在底层实现、性能特点和适用场景上存在显著差异。理解这些差异对于编写高效、健壮的代码至关重要。本文将从底层结构、性能、内存占用和适用场景等方面对 ArrayList​ 和 LinkedList​ 进行详细对比,帮助开发者在实际开发中做出更合理的选择。

e7aaa71660fc45a6fdf801bd272d67d

一、核心区别概览

特性ArrayListLinkedList
底层数据结构动态数组双向链表
内存占用更紧凑(仅存储数据)更高(每个元素带两个指针)
随机访问性能O(1) - 极快O(n) - 较慢
头部插入/删除O(n) - 需要移动元素O(1) - 直接修改指针
尾部插入/删除O(1) - 快速(摊销时间)O(1) - 快速
中间插入/删除O(n) - 需要移动元素O(n) - 需要遍历到位置
迭代器性能快速快速
内存局部性优秀(连续内存)差(元素分散在内存中)

二、底层数据结构详解

1. ArrayList

// 内部实现
transient Object[] elementData; // 存储元素的数组
private int size;              // 当前元素数量
  • 基于动态可扩展数组
  • 初始容量通常为10
  • 当容量不足时自动扩容(通常是原容量的1.5倍)

源码:类属性

public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{//序列号版本号@java.io.Serialprivate static final long serialVersionUID = 8683452581122892189L;//默认初始容量private static final int DEFAULT_CAPACITY = 10;//空数组,指定默认初始化容量为0时赋值给elementData,避免了重复创建空数组private static final Object[] EMPTY_ELEMENTDATA = {};//当调用无参构造方法时,赋值给elementData,主要用于在添加第一个元素前,标记该ArrayList是由无参构造器创建,便于将容量初始化为DEFAULT_CAPACITY,避免了重复创建空数组private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//实际存放元素的数组transient Object[] elementData; // non-private to simplify nested class access//ArrayList的元素个数private int size;
}

ArrayList​是List接口的实现类,底层基于数组实现,容量可根据需要动态增加,相当于动态数组。ArrayList​继承于AbstractList​,并且还实现了Cloneable​、Serializable​、RandomAccess​接口。

image

  • List​:表明是列表数据结构,可以通过下标对元素进行添加删除或查找。
  • Serializable​:表示可以进行序列化和反序列化操作,可以把对象与字节流相互转换。
  • RandomAccess​:有这个接口标记的List表示可以支持快速随机访问,即通过元素下标可以直接得到元素内容。
  • Cloneable​:表示支持拷贝,可以通过浅拷贝或深拷贝来复制对象的副本。

2. LinkedList

// 内部节点结构
private static class Node<E> {E item;         // 存储的数据Node<E> next;   // 指向下一个节点Node<E> prev;   // 指向上一个节点
}// 链表属性
transient Node<E> first; // 头节点
transient Node<E> last;  // 尾节点
transient int size = 0;  // 元素数量
  • 基于双向链表实现
  • 每个元素都包装在节点对象中
  • 节点包含前后指针(增加内存开销)

image

三、性能对比详解

1. 访问元素(get 操作)

// ArrayList 直接通过索引访问数组
public E get(int index) {Objects.checkIndex(index, size);return elementData(index); // 直接数组访问
}// LinkedList 需要遍历链表
public E get(int index) {checkElementIndex(index);return node(index).item; // 需要遍历到指定位置
}
  • ArrayList:O(1) 时间复杂度,直接通过数组索引访问
  • LinkedList:O(n) 时间复杂度,平均需要遍历半个列表

2. 插入/删除操作

尾部操作
// ArrayList 尾部添加
public boolean add(E e) {modCount++;add(e, elementData, size); // 通常为O(1),扩容时O(n)return true;
}// LinkedList 尾部添加
public boolean add(E e) {linkLast(e); // 总是O(1)return true;
}
  • 两者在尾部操作都很高效(O(1))
  • ArrayList 在需要扩容时有临时性能下降
头部操作
// ArrayList 头部插入
public void add(int index, E element) {rangeCheckForAdd(index);modCount++;final int s;Object[] elementData;// 需要移动所有后续元素System.arraycopy(elementData, index,elementData, index + 1,s - index);
}// LinkedList 头部插入
private void linkFirst(E e) {final Node<E> f = first;final Node<E> newNode = new Node<>(null, e, f);first = newNode;if (f == null)last = newNode;elsef.prev = newNode;
}
  • ArrayList:O(n) - 需要移动所有后续元素
  • LinkedList:O(1) - 只需修改几个指针

3. 内存占用

  • ArrayList

    • 每个元素占用约 4 字节(引用大小)
    • 少量额外空间(size 计数等)
    • 可能有未使用的预留空间
  • LinkedList

    • 每个元素需要额外 24 字节(12字节对象头 + 前后指针各4字节 + 4字节数据引用)
    • 内存分散,缓存不友好

4. 迭代性能对比

// 遍历 100,000 个元素的时间对比
List<Integer> arrayList = new ArrayList<>();
List<Integer> linkedList = new LinkedList<>();// 填充数据...long start = System.nanoTime();
for (int i : arrayList) { /* 遍历操作 */ }
long arrayTime = System.nanoTime() - start;start = System.nanoTime();
for (int i : linkedList) { /* 遍历操作 */ }
long linkedTime = System.nanoTime() - start;System.out.println("ArrayList遍历时间: " + arrayTime + "ns");
System.out.println("LinkedList遍历时间: " + linkedTime + "ns");
  • 两者迭代性能接近(O(n))
  • ArrayList 通常更快(更好的内存局部性)
  • LinkedList 在迭代时删除元素更高效

使用场景推荐
使用 ArrayList 当:

✅ 需要频繁随机访问元素(按索引)
✅ 主要在列表尾部添加/删除元素
✅ 内存资源紧张
✅ 需要最小化迭代开销
✅ 元素数量相对稳定

使用 LinkedList 当:

✅ 需要频繁在头部插入/删除元素
✅ 需要实现队列或双端队列功能
✅ 列表中间需要频繁插入/删除
✅ 内存资源充足
✅ 主要使用迭代器进行遍历和修改

最佳实践
  1. 初始化容量(ArrayList):

    // 预估大小以减少扩容次数
    List<String> list = new ArrayList<>(1000);
    
  2. 使用迭代器删除(LinkedList):

    Iterator<Integer> iter = linkedList.iterator();
    while (iter.hasNext()) {if (iter.next() % 2 == 0) {iter.remove(); // O(1) 操作}
    }
    
  3. 避免使用索引循环(LinkedList):

    // 差 - O(n^2) 性能
    for (int i = 0; i < linkedList.size(); i++) {linkedList.get(i); // 每次都是O(n)
    }// 好 - O(n) 性能
    for (Integer num : linkedList) {// 使用增强for循环
    }
    
  4. 考虑替代方案

    // 需要频繁头部操作时
    Deque<String> deque = new ArrayDeque<>(); // 比LinkedList更高效// 需要线程安全时
    List<String> safeList = Collections.synchronizedList(new ArrayList<>());
    

总结

维度ArrayList 优势LinkedList 优势
随机访问⭐⭐⭐⭐⭐ 极快⭐ 很慢
头部操作⭐ 很慢⭐⭐⭐⭐⭐ 极快
内存效率⭐⭐⭐⭐ 较高效⭐ 较低效
迭代性能⭐⭐⭐⭐ 很快⭐⭐⭐ 较快
中间修改⭐⭐ 较慢⭐⭐⭐ 较快(使用迭代器)

简单决策指南

  • 90% 的情况选择 ArrayList(Java 标准库和大多数框架的默认选择)
  • 需要实现队列/栈功能时选择 LinkedListArrayDeque
  • 在极端性能敏感场景进行基准测试

总的来说,ArrayList​ 和 LinkedList​ 各有优缺点,适用的场景也有所不同。选择哪个集合类应根据具体需求来决定。如果你需要频繁进行随机访问,ArrayList​ 是更好的选择;而如果你需要频繁进行插入和删除操作,尤其是在列表的头部或尾部,LinkedList​ 会更合适。

在实际编程中,理解它们的区别并根据需求合理选择数据结构,可以大大提高程序的性能和效率。希望本文能够帮助大家更好地理解这两种常见的 Java 集合类,为编写高效、灵活的代码提供参考。

3ec794dacde34583aafafe15b1490012

http://www.dtcms.com/a/277037.html

相关文章:

  • 完整 Spring Boot + Vue 登录系统
  • 决策树学习
  • Spring Cloud Gateway 实战指南
  • 设计模式深度解析:单例、工厂、适配器与代理模式
  • 基于 Python 的深度学习音乐推荐系统设计与实现
  • LLM对话框项目总结II
  • Maven 构建命令
  • RedisJSON 的 `JSON.ARRAPPEND`一行命令让数组动态生长
  • vue防内存泄漏和性能优化浅解
  • CSS中@media介绍和使用示例
  • 7.13 note
  • 型模块化协作机器人结构设计cad【1张】三维图+设计说明书
  • 机器人猫咪能否温暖中老年孤独
  • 【Complete Search】-基础完全搜索-Basic Complete Search
  • 摩尔线程MUSA架构深度调优指南:从CUDA到MUSA的显存访问模式重构原则
  • Java: OracleHelper
  • Appium源码深度解析:从驱动到架构
  • Vue3 实现文件上传功能
  • HarmonyOS组件/模板集成创新活动-开发者工具箱
  • Vue配置特性(ref、props、混入、插件与作用域样式)
  • FusionOne HCI 23 超融合实施手册(超聚变超融合)
  • 第七章 算法题
  • NO.4数据结构数组和矩阵|一维数组|二维数组|对称矩阵|三角矩阵|三对角矩阵|稀疏矩阵
  • 电源中的声学-噪声,如何抑制开关电源的噪声
  • 飞算JavaAI:开启 Java 开发 “人机协作” 新纪元
  • 二叉树算法详解和C++代码示例
  • 项目合作复盘:如何把项目经验转化为可复用资产
  • 【C++】第十五节—一文详解 | 继承
  • ArkUI Inspector工具用法全解析
  • 【保姆级图文详解】Spring AI 中的工具调用原理解析,工具开发:文件操作、联网搜索、网页抓取、资源下载、PDF生成、工具集中注册