Java 开发者必看!ArrayList 和 LinkedList 的性能厮杀:选错一次,代码慢成蜗牛

简介: 本文深入解析了 Java 中 ArrayList 和 LinkedList 的性能差异,揭示了它们在不同操作下的表现。通过对比随机访问、插入、删除等操作的效率,指出 ArrayList 在多数场景下更高效,而 LinkedList 仅在特定情况下表现优异。文章强调选择合适容器对程序性能的重要性,并提供了实用的选择法则。

就算业务逻辑写得再溜,也可能把应用搞成蜗牛速度。

坑不在算法本身,而在你选的 “容器” 上。

选不对 List,每次循环都像绕远路散步;

选对了,同样的代码能立马精神起来。

ArrayList 和 LinkedList 表面看像双胞胎,

骨子里却像两个物种。

就是这种 “货不对板”,悄悄决定了性能是翻车还是封神。

读完这篇,你就能掌握一个简单法则,不用跑分也能选对。

两个 List 的暗战

ArrayList 是个可扩容的数组,内存里是一整块连续空间,支持直接索引。

LinkedList 是串节点,每个节点存着值和两个指针(.prev 和.next)。

一张图胜过千次微基准测试:

ArrayList(连续内存)

Index:   0     1     2     3     4
Data:   [A]   [B]   [C]   [D]   [E]
Access: direct by index → O(1)

LinkedList(双向链表)

+-----+----+       +-----+----+       +-----+----+
   |  A  | ↔  |  <->  |  B  | ↔  |  <->  |  C  | ↔  |
   +-----+----+       +-----+----+       +-----+----+
Each node has:
- the value
- a pointer to previous
- a pointer to next
Access: must follow links → O(n)

每个节点包含:

  • 实际值
  • 指向前一个节点的指针
  • 指向后一个节点的指针

访问:必须顺着链条找 → O (n)

内存布局几乎决定了所有性能差异。

为啥一个飞似闪电,一个爬如蜗牛

随机访问:

  • ArrayList:O (1)(唰一下就到)。
  • LinkedList:O (n)(得一个个节点挪过去)。

尾部追加:

  • ArrayList:平均 O (1)(偶尔扩容,但总体很快)。
  • LinkedList:O (1)(有尾指针的话,直接挂上去)。

头部插入 / 删除:

  • ArrayList:O (n)(后面所有元素都得挪位置)。
  • LinkedList:O (1)(改两个指针就行,快得很)。

中间插入 / 删除:

除非手里握着迭代器定位到了目标位置,否则俩都得先 “找” 到位置。

如果用 ListIterator 定位好了,LinkedList 实际插入只要 O (1);ArrayList 还是得挪一堆元素。

内存开销:

ArrayList 只存元素本身。

LinkedList 每个元素要带俩指针。数据量到百万级,内存能差出一大截。

所以结论很简单:

多数时候是按索引读,或者往末尾加东西 → 选 ArrayList。

边迭代边修改,或者频繁在头部增删 → 可以瞅瞅 LinkedList。

代码揭露真面目

随机访问:ArrayList 飞,LinkedList 爬。

var a = new ArrayList<Integer>(1000000);
for (int i = 0; i < 1000000; i++) {
    a.add(i);
}
var l = new LinkedList<Integer>();
for (int i = 0; i < 1000000; i++) {
    l.add(i);
}
// 同样操作,成本天差地别
int x = a.get(700000); // O(1) 直接定位
int y = l.get(700000); // O(n) 慢慢爬吧

LinkedList 的秘密:在 “当前位置” 修改才快。

var list = new LinkedList<String>();
list.add("A");
list.add("B");
list.add("D");
// 用迭代器遍历并插入
var it = list.listIterator();
while (it.hasNext()) {
    if (it.next().equals("B")) {
        it.add("C"); // 直接插在B后面
        break;
    }
}

代码评审时常见的坑

  • 用 LinkedList 还写索引循环

如果你写for (int i = 0; i < n; i++) list.get(i),LinkedList 能慢到让你怀疑人生。

换成 ArrayList,或者用迭代器遍历吧。

  • 迷信 “插入快”

LinkedList 只有在 “已经站在目标位置” 时才快。

多数时候,找位置的成本早就抵消了插入的优势。

  • 忽略缓存 locality

ArrayList 的数据在内存里是一整块,CPU 缓存友好得很;

LinkedList 的节点散落在内存各处,访问起来自然慢。

  • 用 LinkedList 实现队列

要做队列,用 ArrayDeque!

它更快、更省空间,就是为这场景设计的。

靠谱的简单法则

  1. 先从 ArrayList 开始

多数情况它都是最佳选择:内存占用小,读操作飞快,用起来还简单。

  1. LinkedList 只在特殊场景用

只有两种情况考虑它:一是边迭代边频繁修改(比如遍历的时候就地增删),二是必须频繁在头部增删。

  1. 队列和栈首选 ArrayDeque

别用 LinkedList 搞这个,ArrayDeque 专门优化了队列和栈操作,快得多。

  1. 改之前先实测

别瞎猜!先 profiling 看看代码到底慢在哪。很多时候瓶颈根本不在 List 本身。

这几条能搞定 Java 里大部分 List 性能问题。

选错 List 让 get (i) 变龟速的真实案例

有个同事用 LinkedList 存用户 ID,觉得它 “更灵活”。

后来他写了个循环,反复调用list.get(i)检查每个 ID。

在 LinkedList 上,每次 get (i) 都得从头爬一遍。

用户多到几千时,程序慢得像卡住了。

我们换成 ArrayList,get (i) 变成常数时间,

同样的代码瞬间变快,其他啥都没改。

就因为换了个 List 而已。

最终判决:默认选手 vs 专精选手

ArrayList 是日常主力,

LinkedList 是那种 “只租一个周末干活” 的专用工具。

如果你的访问模式是频繁按索引读,或者往末尾加元素,ArrayList 完胜。

如果你的修改操作集中在当前位置,而且用迭代器操作,LinkedList 能超常发挥。

摸清自己的使用模式,按内存布局选,别被传言忽悠。

相关文章
|
4月前
|
Java 测试技术 API
Java Stream API:被低估的性能陷阱与优化技巧
Java Stream API:被低估的性能陷阱与优化技巧
408 114
|
4月前
|
Java 开发工具
【Azure Storage Account】Java Code访问Storage Account File Share的上传和下载代码示例
本文介绍如何使用Java通过azure-storage-file-share SDK实现Azure文件共享的上传下载。包含依赖引入、客户端创建及完整示例代码,助你快速集成Azure File Share功能。
410 5
|
5月前
|
IDE Java 关系型数据库
Java 初学者学习路线(含代码示例)
本教程为Java初学者设计,涵盖基础语法、面向对象、集合、异常处理、文件操作、多线程、JDBC、Servlet及MyBatis等内容,每阶段配核心代码示例,强调动手实践,助你循序渐进掌握Java编程。
639 3
|
5月前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
599 3
|
5月前
|
安全 Java API
Java Web 在线商城项目最新技术实操指南帮助开发者高效完成商城项目开发
本项目基于Spring Boot 3.2与Vue 3构建现代化在线商城,涵盖技术选型、核心功能实现、安全控制与容器化部署,助开发者掌握最新Java Web全栈开发实践。
535 1
|
5月前
|
Java API 开发工具
【Azure Developer】Java代码实现获取Azure 资源的指标数据却报错 "invalid time interval input"
在使用 Java 调用虚拟机 API 获取指标数据时,因本地时区设置非 UTC,导致时间格式解析错误。解决方法是在代码中手动指定时区为 UTC,使用 `ZoneOffset.ofHours(0)` 并结合 `withOffsetSameInstant` 方法进行时区转换,从而避免因时区差异引发的时间格式问题。
288 3
|
4月前
|
Java 数据处理 API
为什么你的Java代码应该多用Stream?从循环到声明式的思维转变
为什么你的Java代码应该多用Stream?从循环到声明式的思维转变
299 115
|
4月前
|
安全 Java 编译器
为什么你的Java代码需要泛型?类型安全的艺术
为什么你的Java代码需要泛型?类型安全的艺术
214 98
|
5月前
|
Java
java入门代码示例
本文介绍Java入门基础,包含Hello World、变量类型、条件判断、循环及方法定义等核心语法示例,帮助初学者快速掌握Java编程基本结构与逻辑。
477 0