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
相关推荐
代码游侠11 分钟前
学习笔记——设备树基础
linux·运维·开发语言·单片机·算法
mango_mangojuice13 分钟前
Linux学习笔记(make/Makefile)1.23
java·linux·前端·笔记·学习
程序员侠客行17 分钟前
Mybatis连接池实现及池化模式
java·后端·架构·mybatis
devmoon19 分钟前
运行时(Runtime)是什么?为什么 Polkadot 的 Runtime 可以被“像搭积木一样”定制
开发语言·区块链·智能合约·polkadot·runtmie
时艰.20 分钟前
Java 并发编程 — 并发容器 + CPU 缓存 + Disruptor
java·开发语言·缓存
丶小鱼丶25 分钟前
并发编程之【优雅地结束线程的执行】
java
市场部需要一个软件开发岗位30 分钟前
JAVA开发常见安全问题:Cookie 中明文存储用户名、密码
android·java·安全
忆~遂愿34 分钟前
GE 引擎进阶:依赖图的原子性管理与异构算子协作调度
java·开发语言·人工智能
沐知全栈开发38 分钟前
API 类别 - 交互
开发语言
MZ_ZXD00139 分钟前
springboot旅游信息管理系统-计算机毕业设计源码21675
java·c++·vue.js·spring boot·python·django·php