263. Java 集合 - 遍历 List 时选用哪种方式?ArrayList vs LinkedList

文章目录

  • [263. Java 集合 - 遍历 List 时选用哪种方式?`ArrayList` vs `LinkedList`](#263. Java 集合 - 遍历 List 时选用哪种方式?ArrayList vs LinkedList)
      • [🔁 两种常见遍历方式](#🔁 两种常见遍历方式)
        • [🟦 方式一:使用索引 `for (int i = 0; i < size; i++)`](#🟦 方式一:使用索引 for (int i = 0; i < size; i++))
        • [🟩 方式二:使用增强 `for`(隐式 `Iterator`)](#🟩 方式二:使用增强 for(隐式 Iterator))
      • [🔍 对于 `ArrayList`,两种方式几乎一样](#🔍 对于 ArrayList,两种方式几乎一样)
        • [⏱ 性能对比(1000 元素)](#⏱ 性能对比(1000 元素))
      • [⚠️ 对于 `LinkedList`,差别巨大!](#⚠️ 对于 LinkedList,差别巨大!)
        • [⏱ 性能对比(`1000` 元素)](#⏱ 性能对比(1000 元素))
      • [🔄 是否应该复制 `LinkedList` 再遍历?](#🔄 是否应该复制 LinkedList 再遍历?)
      • [⏱ 基准测试:复制后再迭代](#⏱ 基准测试:复制后再迭代)
      • [🧠 性能背后的本质:`Pointer Chasing & Cache Miss`](#🧠 性能背后的本质:Pointer Chasing & Cache Miss)
        • [💡 为什么 `LinkedList` 慢?](#💡 为什么 LinkedList 慢?)
      • [🧾 最终建议总结表格](#🧾 最终建议总结表格)

263. Java 集合 - 遍历 List 时选用哪种方式?ArrayList vs LinkedList


🔁 两种常见遍历方式

我们有两种遍历方式:

🟦 方式一:使用索引 for (int i = 0; i < size; i++)
java 复制代码
for (int index = 0; index < ints.size(); index++) {
   var v = ints.get(index); // 读取第 index 个元素
}

✅ 适用于支持随机访问的集合,如 ArrayList

🟩 方式二:使用增强 for(隐式 Iterator
java 复制代码
for (var v : ints) {
   // v 是集合中的每个元素
}

✅ 实际上等价于使用 Iterator,但 JVM 编译器可对其优化。


🔍 对于 ArrayList,两种方式几乎一样

因为 ArrayList 底层是数组,所以:

  • ints.get(i) 实际是数组索引读取(O(1)
  • Iterator 本质上也是一层封装,差别极小
⏱ 性能对比(1000 元素)
遍历方式 耗时 (ms/op)
Iterator (for-each) 1.447
Index 访问 1.986

📌 小结:

  • 虽然 index 方式管理变量稍重,但差异不大;
  • JIT 编译器可以省略 Iterator 对象的创建 ,提高 for-each 效率。

⚠️ 对于 LinkedList,差别巨大!

LinkedList.get(index) 每次都从头或尾走一遍链表,因此:

  • list.get(i)O(n) 操作
  • 遍历中调用 get(i) n 次 → 总复杂度 O(n²) 😱
⏱ 性能对比(1000 元素)
遍历方式 耗时 (ms/op)
Iterator (for-each) 4.950
Index 访问 584.889

💥 足足慢了 100 倍!

🧠 类比讲解建议:

就像走迷宫,每走一步都得重新从入口出发 ------ 耗时自然爆炸。

📌 总结建议:

  • ❌ 千万不要用 get(i) 遍历 LinkedList
  • ✅ 使用 Iterator 才是正解

🔄 是否应该复制 LinkedList 再遍历?

既然 LinkedList 指针太慢,那有没有可能......先复制成 ArrayList 再遍历?🤔

试试看这个例子:

java 复制代码
var ints = IntStream.range(0, 1000)
                    .boxed()
                    .collect(Collectors.toCollection(LinkedList::new));

var copy = ints.stream().toList(); // 复制成 List(底层数组)
for (var v : copy) {
   // 访问元素
}

📌 为什么这招有效?

  • toList() 会直接创建一个刚好大小的数组,效率高;
  • 创建过程耗时低,只有迭代一次的 4%
  • 如果需要遍历多次,复制成本很快就能摊平。

⏱ 基准测试:复制后再迭代

遍历次数 耗时 (ms/op) 说明
1 5.182 一次遍历,复制成本占 4%
10 14.031 很快 amortize 掉复制成本
100 100.104 相比原始遍历快了很多

📌 建议:

如果你要遍历多次、或者做随机读取,复制 LinkedList 是划算的!


🧠 性能背后的本质:Pointer Chasing & Cache Miss

💡 为什么 LinkedList 慢?

因为每个元素都在堆内存的不同位置,遍历时:

  • 需要不断"追踪"下一节点地址(pointer chasing);
  • 容易触发 CPU 的 cache miss,大幅增加访问延迟。

🧠 类比讲解建议:

就像你家书架有编号,但每本书放在不同房间,还要问人借钥匙开门 ------ 慢!

📌 对比数组结构:

特性 ArrayList LinkedList
元素位置 连续内存 离散内存(靠引用串联)
读取成本 固定偏移 → 快(O(1) 多次跳转 → 慢(O(n)
遍历友好性 ✅ 高 ❌ 差

🧾 最终建议总结表格

操作场景 建议使用容器 建议遍历方式
随机读取、频繁访问元素 ArrayList ✅ index / for-each
插入/删除频繁(头/尾) LinkedList ✅ iterator`
遍历一次 都可接受 LinkedList 别用 index
遍历多次 ArrayList 或先复制 for-each
大规模遍历性能敏感 ArrayList for-each
相关推荐
Tony Bai1 天前
高并发后端:坚守 Go,还是拥抱 Rust?
开发语言·后端·golang·rust
wjs20241 天前
Swift 类型转换
开发语言
没有bug.的程序员1 天前
服务安全:内部服务如何防止“裸奔”?
java·网络安全·云原生安全·服务安全·零信任架构·微服务安全·内部鉴权
一线大码1 天前
SpringBoot 3 和 4 的版本新特性和升级要点
java·spring boot·后端
秃了也弱了。1 天前
python实现定时任务:schedule库、APScheduler库
开发语言·python
weixin_440730501 天前
java数组整理笔记
java·开发语言·笔记
weixin_425023001 天前
Spring Boot 实用核心技巧汇总:日期格式化、线程管控、MCP服务、AOP进阶等
java·spring boot·后端
一线大码1 天前
Java 8-25 各个版本新特性总结
java·后端
Thera7771 天前
状态机(State Machine)详解:原理、优缺点与 C++ 实战示例
开发语言·c++
2501_906150561 天前
私有部署问卷系统操作实战记录-DWSurvey
java·运维·服务器·spring·开源