数组是jvm层面固定长度的连续内存结构,直接存储基本类型或引用;集合是java封装的动态容器,仅存对象、支持自动扩容,但引入解引用和gc开销。

数组和集合的差异,本质是程序逻辑与硬件物理特性的映射关系:数组直接依赖连续物理内存实现高效访问,而集合通过动态变量(如引用、指针、扩容数组)在逻辑层绕开硬件限制,但引入了额外的间接寻址与内存管理开销。
数组靠连续内存实现O(1)随机访问
数组在JVM堆中申请一块固定大小、地址连续的内存空间。CPU通过基地址 + 索引 × 元素字节长度,一步算出目标地址,无需查表或跳转。这种计算式寻址完全匹配现代CPU的缓存行(cache line)预取机制——一次加载相邻多个元素,大幅提升局部性效率。例如 int[1000] 中访问 arr[500],硬件直接定位到第500个4字节偏移处,无任何对象头、引用解引用或边界检查(编译期已知长度)。
集合用对象引用间接访问,打破内存连续性
以 ArrayList 为例,它内部持有一个 Object[] elementData 引用,该数组本身是连续的,但数组中每个元素存的是对象的内存地址(引用),而非对象本体。这意味着:
- 访问 list.get(i) 需先定位 elementData[i](连续数组访问),再根据其中存储的地址去另一块不相关内存读取实际对象——产生一次额外的指针解引用;
- 若对象本身分散在堆中(如频繁 new 出来的 String),就会导致缓存不命中(cache miss),访问延迟显著上升;
- elementData 是堆上独立对象,其生命周期、GC标记、内存页分配均与外围 List 实例解耦,硬件无法对“list → elementData → 实际对象”这一链路做一体化优化。
动态扩容不是“自动变长”,而是硬件重分配+数据搬迁
ArrayList 的扩容(如从10扩到15)并非修改原有内存块长度,而是由JVM向操作系统重新申请一块更大的连续内存,再把原数组内容逐字节复制过去。这个过程涉及:
- 系统调用 mmap 或 brk 请求新页框(page frame);
- 内存管理单元(MMU)更新页表项,映射新虚拟地址;
- CPU执行数组拷贝循环(底层常优化为 memcpy 指令),消耗周期与数据量成正比;
- 原小数组变成垃圾,等待GC回收,可能触发年轻代收集甚至内存碎片整理。
Vector 扩容翻倍、ArrayList 扩1.5倍,本质是在“减少扩容频次”和“避免内存浪费”之间做硬件资源权衡。
基本类型数组享有硬件级优化,包装类集合则失去该优势
int[] 在内存中就是连续排列的32位整数,CPU可直接用 SIMD 指令批量运算(如加法、求和)。而 ArrayList










