java数组是堆中连续内存块,数组变量存首地址,寻址用o(1)公式计算;二维数组是“数组的数组”,各一维数组独立分配,非纯连续结构。

Java 中的数组不是一堆变量的简单集合,而是一块连续分配在堆内存中的数据块,其高效性正源于这种物理布局和确定的寻址逻辑。
数组变量只存地址,真实数据全在堆里
声明 int[] arr = new int[5]; 时,变量 arr 本身存在栈中,它里面存储的只是一个数值——即堆中那块连续内存的起始地址(比如 0x80000000)。真正的 5 个整数(每个占 4 字节)被紧挨着放在堆里,形成一段长度为 20 字节的连续空间。
这意味着:
- 数组名不是数据,只是“门牌号”;
- 多个数组变量可以指向同一段堆内存(如 arr2 = arr1),改一个就影响另一个;
- 数组对象本身(含长度、类型等元信息)也存于堆中,但和元素数据分开管理。
寻址靠公式,不靠遍历
访问 arr[3] 时,JVM 不是从头数到第四个,而是直接计算内存地址:
元素地址 = 首地址 + 索引 × 单个元素字节数
对 int[] 来说,就是 0x80000000 + 3 × 4 = 0x8000000C。这个运算由 CPU 在一个指令周期内完成,所以随机访问时间复杂度恒为 O(1)。
注意:
- 索引从 0 开始,是为了让公式简洁(避免每次加减 1);
- 无论数组长度是 10 还是 100 万,算第 999999 个元素的地址,耗时一样;
- 对于 Object[],每个槽位存的是 4 字节(32 位 JVM)或 8 字节(64 位开启压缩指针)的对象引用,寻址逻辑不变。
连续布局带来优势,也带来代价
连续内存让数组成为 Java 中查询最快的原生结构,几乎所有集合类(ArrayList、HashMap 的桶数组等)都依赖它实现底层支撑。
但连续性也决定了它的硬约束:
- 插入/删除中间元素需移动后续所有项,平均耗时 O(n);
- 创建时必须预估大小,扩容需申请新连续空间 + 复制旧数据;
- 大数组容易触发老年代分配(尤其超过 TLAB 或 Eden 容量时)。
二维数组本质是“数组的数组”
声明 int[][] matrix = new int[3][4]; 实际创建了一个长度为 3 的 int[] 数组,每个元素又是一个长度为 4 的 int[] 对象。这些一维数组各自在堆中独立分配,彼此不一定相邻。
因此它的寻址分两步:
- 先定位到第 i 个一维数组引用:base + i × 8(64 位下引用占 8 字节);
- 再在该一维数组中按公式找第 j 个元素:row_base + j × 4。
这说明:二维数组不是一块 3×4=12 个整数的纯连续内存,而是一层引用+多块小连续区的组合结构。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











