课程链接:黑马程序员java零基础[上]
1.二维数组的内存分布
在 Java 中,二维数组并不是一整块连续的二维空间,而是数组的数组。具体而言,在声明一个二维数组:如int[][] arr = new int[2][3];
时,内存中会发生如下:
1.1 栈上的引用变量
首先,在栈内存中,JVM(java虚拟机) 会创建一个名为 arr 的引用变量。它不存实际数据,只是指向堆上的数组对象。
1.2 堆上的顶层数组
在 Java 中,所有通过 new
创建的对象,包括数组,都是在堆内存中分配的。对于二维数组,JVM 会先在堆上创建一个长度为 2 的一维数组:
- 这个数组的特殊之处在于,它的元素类型是 int[],也就是“一维整型数组的引用”。
- 栈上的 arr 就指向这个一维数组的首地址。
1.3 行数组与元素
接下来,JVM 会为每一行分别创建一个长度为 3 的 一维数组对象:
arr[0] 指向第一行数组,内部存放 arr[0][0]、arr[0][1]、arr[0][2]的值(默认初始化为 0)。
arr[1] 指向第二行数组,内部存放第二行的数据,依此类推。
注意:每一行数组内部的元素是连续存放的。
不同的行在堆上可能不连续。
1.4 访问数组元素
访问数组时遵循“先行后列”的逻辑:
arr[i] → 找到第 i 行数组的引用。
arr[i][j] → 在该行数组里找到第 j 个元素。
所以,二维数组本质上是数组的数组:外层数组管理“行”,内层数组存数据。
2.二维数组的特殊写法
二维数组除了标准写法和简化写法外还存在更灵活的用法。
2.1交错数组:
标准的二维数组每一行的列数都是相同的,但在实际应用中,每一行的元素个数可以不同。
实现方式:
在初始化二维数组时,只指定其“行数”(即顶层数组的长度),而暂时不指定“列数”。
// 1. 只定义行数,此时 arr[0], arr[1] 都还是 null
int[][] arr = new int[2][]; // 需要定义数组后再手动创建该二位数组下的2个一维数组
int[] arr0 = {11, 22};
int[] arr1 = {11, 22, 33};
// 2. 手动为每一行分别创建不同长度的一维数组
arr[0] = arr0;
arr[1] = arr1;
此时堆内存和栈内存中的分布为:
优点:
这种写法的最大优点是高度的灵活性。它允许我们根据实际需求精确控制每一行数组的长度,从而有效节约内存空间,避免为不存在的元素分配内存。
2.2行引用的重新赋值:
既然二维数组的每一行本身就是一个独立的数组引用,那么我们就可以将这个引用指向任何其他兼容的一维数组对象。
实现方式:
先创建一个标准的二维数组,然后再用其他已经存在的一维数组引用来“覆盖”或“替换”它的某几行。
int[][] arr = new int[2][3];
int[] arr1 = {11, 22};
int[] arr2 = {33, 44, 55};
arr[0] = arr1;
arr[1] = arr[2];
此时堆内存和栈内存中的分布为:
优点:
这种写法展示了 Java 引用的强大之处。它不是用新数组的“值”去覆盖原数组,而是直接替换“行”的引用,将二维数组的某一行与一个独立的一维数组关联起来。原本不再被引用的行数组会由 GC 自动回收,无需手动管理内存。