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
相关推荐
乌暮1 小时前
深入理解 Java 泛型:把「万能盒子」用对、用稳
java·开发语言·后端·学习
大猫和小黄1 小时前
深入理解 Java Optional:从设计哲学到工程实战
java
数据库百宝箱1 小时前
rum&gin索引对比
java·数据库·gin
2601_962218611 小时前
C语言中三种库的编译和使用方法
开发语言·c++
传奇开心果编程2 小时前
中庸、有容、破执:仓颉编程语言的中华智慧结晶——三重哲学境界思辨
开发语言·华为·开源·软件工程
yychen_java2 小时前
第六篇:Spring AI 实战:将 Java 业务接口封装成企业级 MCP Server
java·人工智能·spring
jimy12 小时前
派生类重写基类虚函数(二):override的作用
开发语言·c++
_upupup2 小时前
异常(C++)
java·开发语言·jvm
g10565591392 小时前
公有云_云运维服务
java·运维·服务器
霸道流氓气质2 小时前
ComfyUI 图像生成工作流完全指南:从节点编排到Java生产级图像生产实战
java·开发语言·人工智能