1. 锯齿状数组与二维数组的区别
我们来到了一个很多人称为“锯齿状数组”的主题——英文称为 jagged arrays。与二维数组不同,锯齿状数组允许每一行的长度不同。可以把它想象成一个由多栋建筑组成的小区,每栋楼的房间数各不相同——一栋楼有 5 套,另一栋有 20 套,还有一栋只有一套。
锯齿状数组是一个数组,其中每个元素本身又是一个数组,而且这些内层数组(也称为“子数组”)的长度可以不同。
主要区别:
- 在二维数组中,每一“行”(以及每一“列”)的元素数量相同。 例如:int[][] grid = new int[3][5]; —— 我们始终有 3 行、每行 5 个元素。
- 在锯齿状数组中,每一行的长度都可以不同! 例如:int[][] jagged = new int[3][]; —— 然后我们再分别初始化每一行(子数组)。
直观示意如下:
二维数组 (3x3):
┌───┬───┬───┐
│ 1 │ 2 │ 3 │
├───┼───┼───┤
│ 4 │ 5 │ 6 │
├───┼───┼───┤
│ 7 │ 8 │ 9 │
└───┴───┴───┘
锯齿状数组(行长不同):
┌───┬───┐
│ 1 │ 2 │
├───┼───┼───┬───┐
│ 3 │ 4 │ 5 │ 6 │
├───┼───┴───┴───┘
│ 7 │
└───┘
2. 锯齿状数组的声明与初始化语法
声明锯齿状数组并不比之前的类型更可怕!别被两个方括号吓到:
int[][] jaggedArray = new int[3][];
这意味着我们有一个包含 3 个元素的数组,而且每个元素本身也是一个int 数组。但此时内层数组还没有被创建!为更好地理解,我们来分步骤看看。
锯齿状数组的逐步初始化
步骤 1 —— 创建主(外层)数组:
int[][] jaggedArray = new int[3][];
现在我们有 3 行,但它们都还是 null。
步骤 2 —— 创建并填充内层数组(子数组):
例如,第一行长度为 2,第二行为 4,第三行为 3:
jaggedArray[0] = new int[2]; // 第一行 2 个元素
jaggedArray[1] = new int[4]; // 第二行 4 个元素
jaggedArray[2] = new int[3]; // 第三行 3 个元素
步骤 3 —— 赋值:
内层数组就是普通数组!例如:
jaggedArray[0][0] = 1;
jaggedArray[0][1] = 2;
jaggedArray[1][0] = 3;
jaggedArray[1][1] = 4;
jaggedArray[1][2] = 5;
jaggedArray[1][3] = 6;
jaggedArray[2][0] = 7;
jaggedArray[2][1] = 8;
jaggedArray[2][2] = 9;
锯齿状数组的简洁初始化
如果你已知所有值,也可以一次性创建并填充锯齿状数组:
int[][] jaggedArray = new int[][]
{
new int[] { 1, 2 },
new int[] { 3, 4, 5, 6 },
new int[] { 7, 8, 9 }
};
或者更简洁一些,省略内层数组的类型:
int[][] jaggedArray =
{
{ 1, 2 },
{ 3, 4, 5, 6 },
{ 7, 8, 9 }
};
3. 遍历与操作锯齿状数组
遍历锯齿状数组并不比遍历二维数组更难,只是现在外层循环按行遍历,内层循环遍历该行的元素(每行长度可能不同):
for (int i = 0; i < jaggedArray.length; i++)
{
System.out.println("行 " + i + ":");
for (int j = 0; j < jaggedArray[i].length; j++)
{
System.out.print(jaggedArray[i][j] + " ");
}
System.out.println();
}
屏幕上的结果:
行 0:
1 2
行 1:
3 4 5 6
行 2:
7 8 9
你也可以使用 for-each,这样就不用关心索引:
for (int[] row : jaggedArray)
{
for (int value : row)
{
System.out.print(value + " ");
}
System.out.println();
}
4. 锯齿状数组的典型使用场景
什么时候锯齿状数组比二维数组更合适?
- 当你为每个用户存储不同数量的数据:例如各科成绩、购买记录、评论等。
- 当你的数据呈现三角形或阶梯状结构(例如打印金字塔、帕斯卡三角形等)。
- 当你希望节省内存:二维数组的各行长度固定,而锯齿状数组只分配所需的元素数量。
真实示例:学生成绩管理器
假设我们有三名学生,他们在数学不同作业上的成绩如下:
| 学生 | 成绩 |
|---|---|
| 0 | 5,4 |
| 1 | 3,4,4 |
| 2 | 5 |
声明这样的数组:
int[][] studentMarks = new int[3][];
studentMarks[0] = new int[] { 5, 4 }; // 第 1 个学生——2 个成绩
studentMarks[1] = new int[] { 3, 4, 4 }; // 第 2 个学生——3 个成绩
studentMarks[2] = new int[] { 5 }; // 第 3 个学生——1 个成绩
输出每位学生的成绩:
for (int i = 0; i < studentMarks.length; i++)
{
System.out.print("学生 " + i + ": ");
for (int j = 0; j < studentMarks[i].length; j++)
{
System.out.print(studentMarks[i][j] + " ");
}
System.out.println();
}
与其他类型一起使用锯齿状数组
锯齿状可以是任何类型的数组:字符串、更深层的数组的数组,甚至你自己的对象。
示例:字符串数组
String[][] groups = {
{ "伊万", "彼得" },
{ "玛丽娅", "阿列克谢", "谢尔盖" },
{ "瓦西里萨" }
};
5. 三维与多维数组
关于数组还有一个你可能已经想到的事实。如果可以创建二维数组,那么能否创建三维数组呢?
答案是可以,你可以创建任意维度的数组。这类数组称为多维数组。
如何声明多维数组
只需用方括号写出所需的维度:
int[][][] cube = new int[2][3][4]; // 2 个“层”、3 行、4 列
cube[0][1][2] = 99;
这里我们有一个三维数组:
- 第一维有 2 个元素,
- 第二维有 3 个,
- 第三维有 4 个。
这样的数组就像一个被顺序存放的数据“大立方体”。
遍历三维数组
访问元素时需要同时给出所有索引:
for (int i = 0; i < cube.length; i++)
{
for (int j = 0; j < cube[i].length; j++)
{
for (int k = 0; k < cube[i][j].length; k++)
{
System.out.print(cube[i][j][k] + " ");
}
System.out.println();
}
System.out.println("---");
}
- 索引从 0 开始,这在 Java 中一贯如此。
- 该数组共有 2 × 3 × 4 = 24 个元素。
多维数组的实际示例
- 2D —— 表格、棋盘、图像。
- 3D —— 计算机图形中的“小方块”、科研计算的数据(例如不同空间与时间点的温度)。
- 4D 及以上 —— 很少使用,但会出现在高等数学、仿真、机器学习等领域。
6. 使用多维数组的常见错误
错误 1:数组越界
最常见的错误——尝试访问不存在的元素,例如:
int[][] arr = new int[2][3];
arr[2][0] = 5; // 错误!没有索引为 2 的行(只有 0 和 1)
arr[0][3] = 7; // 错误!没有索引为 3 的列(只有 0、1、2)
此类访问会抛出 ArrayIndexOutOfBoundsException。务必检查索引是否在允许范围内:从 0 到 length - 1。
错误 2:锯齿状数组中的行未初始化
如果只创建了锯齿状数组却忘了初始化内层数组,那么在访问时会得到 NullPointerException:
int[][] jagged = new int[3][];
jagged[0][0] = 5; // 错误!jagged[0] == null
需要先创建内层数组:jagged[0] = new int[2];
错误 3:错误使用数组长度
容易把 matrix.length(行数)和 matrix[0].length(列数)混淆。复制、遍历、按列求和时尤其常见。
错误 4:假设所有行长度都相同
在锯齿状数组中,各行长度可能不同!如果你写 matrix[i][j],请确保 j < matrix[i].length。
错误 5:索引次序混淆
有时会把行列顺序弄反:应该先行后列,即 matrix[row][column],不是相反!
GO TO FULL VERSION