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
相关推荐
JH30732 小时前
Redisson vs Jedis vs Lettuce
java·redis
骇客野人2 小时前
java对象和JSON对象之间的转换关系
java·开发语言·json
红石榴花生油2 小时前
Linux服务器权限与安全核心笔记
java·linux·前端
sumAll2 小时前
拒绝黑盒!Spring @Scheduled 定时任务源码深度解析
java·后端·spring
Seven972 小时前
剑指offer-47、求1+2+3...+n
java
ZePingPingZe2 小时前
Spring boot2.x-第05讲番外篇:常用端点说明
java·spring boot·后端
Macbethad2 小时前
WPF 工业设备管理程序技术方案
java·大数据·hadoop
lubiii_2 小时前
Aircrack-ng工具使用原理与实操笔记
开发语言·网络·web安全·php