多维数组
本教程共 100 篇 · 第 26 篇 · 更新于 2026-08-05 · 约 6 分钟阅读
26. 多维数组
本节目标:理解二维数组就是”数组的数组”,看清它在内存里的真实样子,并学会处理每行长度不同的不规则数组。
一维数组排成一排。要表示”表格”——比如 3 行 4 列的矩阵——就得用二维数组。很多初学者以为二维数组是块整齐的矩形内存,其实在 Java 里,二维数组本质是”数组里面再装数组”。这个认知对了,后面所有坑都好理解。
声明与初始化
int[][] matrix = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
```java
`int[][]` 读作"int 的二维数组"。外层每个元素又是一个一维数组。也可以动态创建:
```java
int[][] m = new int[3][4]; // 3 行,每行 4 列,默认全 0
这种一次给定行和列的写法,会同时把每一行都 new 好,用起来最方便。
Note
new int[3][4]也可以写成new int[3][]然后逐行 new。后者能造出每行长度不一样的不规则数组,下面会讲到。
内存里的真实样子
二维数组不是一块整齐的矩形内存。matrix 这个变量指向一个长度为 3 的”行数组”,而行数组的每个元素分别指向各自的一维数组。
matrix ──> [ ref0, ref1, ref2 ]
| | |
v v v
[1,2,3,4] [5,6,7,8] [9,10,11,12]
```java
所以访问元素是 `matrix[行][列]`:
```java
System.out.println(matrix[0][1]); // 2,第 0 行第 1 列
matrix[1][2] = 99; // 改第 1 行第 2 列
理解这个”两层引用”的结构,是看懂不规则数组和遍历越界的前提。
不规则数组
因为”每行是一个独立数组”,每行长度可以不一样。这叫不规则(锯齿)数组。
int[][] tri = new int[3][]; // 先定 3 行,列数后补
tri[0] = new int[1];
tri[1] = new int[2];
tri[2] = new int[3];
tri[1][1] = 7;
```bash
第一行 1 列、第二行 2 列、第三行 3 列,像三角形。这种灵活度是 C 语言那种"真二维数组"没有的。很多真实数据就是不规则的,比如一个班级里每个学生选的课程数不同。
> [!TIP]
> 动态建不规则数组时,记得先给 `tri[i]` 各自 `new` 一个一维数组,否则它还是 null,访问会抛 `NullPointerException`。
## 遍历二维数组
外层走行,内层走列:
```java
for (int i = 0; i < matrix.length; i++) { // 行
for (int j = 0; j < matrix[i].length; j++) { // 列,用 matrix[i].length
System.out.print(matrix[i][j] + " ");
}
System.out.println();
}
内层长度用 matrix[i].length 而不是固定数,这样不规则数组也能正确遍历。把 matrix[i] 看成一个普通一维数组,二维遍历其实就是”对每一行,做一遍一维遍历”。
Warning遍历二维数组别写死内层长度(如
j < 4)。一旦遇上不规则数组或行列数变化,就会越界或漏元素。永远用matrix[i].length,让每行自己决定宽度。
增强 for 也能遍历二维
for (int[] row : matrix) { // 每次取出一整行(一维数组)
for (int val : row) { // 再遍历这一行
System.out.print(val + " ");
}
System.out.println();
}
```bash
写法更简洁,同样不会越界。"行"是个一维数组,内层增强 for 自己知道到哪结束。
## 三维及以上
理论上还能有三维数组 `int[][][]`,表示"数组的数组的数组",可以想象成"一本书的每一页是一行,每行是一排字"。但实际开发超过二维就很少用,真到了那种复杂度,往往该用对象或集合来表达更清楚了。强行用高维数组,代码会很难读也很难维护。
```java
int[][][] cube = new int[2][3][4]; // 2 层,3 行,4 列
三维数组的遍历就是三层嵌套 for,原理和二维一样,每层用对应维度的 length。
常见错误
- 忘了给不规则数组的每一行 new,直接访问报空指针。
- 内层循环写死长度,遇到不规则数组就出错。
- 把
matrix.length当成”总元素个数”,其实它只是”行数”。 - 以为二维数组一定每行等长,其实不是。
Note二维数组在 Java 里永远是一维数组的数组,没有真正的”矩形内存”。记住这一点,不规则数组、遍历、赋值全部顺理成章。
求二维数组的行数和列数
int rows = matrix.length; // 行数
int cols = matrix[0].length; // 列数(假设至少一行)
```java
如果可能没有行,要先判断 `rows > 0` 再取列数,否则越界。
不规则数组求"总元素数"得累加:
```java
int total = 0;
for (int i = 0; i < tri.length; i++) {
total += tri[i].length; // 每行长度不同,逐个加
}
Java 25 LTS 与老写法对照
二维数组的语法从 Java 1.0 起就没变过。但老代码里有些写法现在不推荐了:
| 写法 | 状态 | 说明 |
|---|---|---|
int[][] arr | ✅ 推荐 | 中括号跟类型 |
int arr[][] | ⚠️ 不推荐 | 能跑,但不直观 |
int[] arr[] | ⚠️ 不推荐 | 更不直观 |
new int[3][4] | ✅ 推荐 | 同时指定行列 |
new int[3][] 再逐行 new | ✅ 推荐 | 不规则数组专用 |
Java 21 引入了 Stream API,也能处理二维数组:
import java.util.Arrays;
int[][] matrix = {{1,2},{3,4}};
// 把二维数组打平成一维流
Arrays.stream(matrix).flatMapToInt(Arrays::stream).forEach(System.out::println);
```bash
这是函数式写法,初学了解即可,后面学 Stream 时会展开。
## 二维数组的适用场景
什么时候用二维数组?
- 棋盘、矩阵、迷宫等网格数据
- 像素图片(每个像素一个值)
- 表格数据(行=记录,列=字段)
- 动态规划里的状态表
- 图的邻接矩阵表示
如果数据是"对象"(比如每个格子有多个属性),用对象数组或 List 更合适。二维数组适合"纯数字、结构规整"的场景。
## 二维数组的浅拷贝陷阱
二维数组的拷贝比一维数组复杂。直接 `Arrays.copyOf` 只复制外层引用,内层数组还是共享的:
```java
int[][] original = {{1,2},{3,4}};
int[][] copy = Arrays.copyOf(original, original.length);
copy[0][0] = 99;
System.out.println(original[0][0]); // 99,被改了!
这叫”浅拷贝”。要”深拷贝”得逐行复制:
int[][] deepCopy = new int[original.length][];
for (int i = 0; i < original.length; i++) {
deepCopy[i] = Arrays.copyOf(original[i], original[i].length);
}
```bash
第 27 章会详细讲 `Arrays.copyOf` 的用法。
## 速查表
| 操作 | 写法 | 说明 |
|------|------|------|
| 声明二维 | `int[][] arr;` | 两对中括号 |
| 静态初始化 | `{{1,2},{3,4}}` | 每行用花括号 |
| 动态初始化 | `new int[3][4]` | 同时指定行列 |
| 不规则 | `new int[3][]` | 只指定行,列后补 |
| 取行数 | `arr.length` | 外层数组长度 |
| 取列数 | `arr[0].length` | 第一行的长度 |
| 遍历 | 双层 for | 内层用 arr[i].length |
| 取总元素数 | 逐行累加 | 不规则数组专用 |
| 深拷贝 | 逐行 copyOf | 浅拷贝会共享内层数组 |
## 常见疑问
**Q:二维数组一定要每行等长吗?**
不一定。Java 的二维数组本质是"数组的数组",每行独立,可以不等长。只有 `new int[r][c]` 这种写法才保证等长。
**Q:能定义四维或更高维数组吗?**
语法上能,`int[][][][]` 这样的声明合法。但超过二维可读性急剧下降,工程里基本不用。遇到高维数据优先考虑对象建模。
**Q:如何复制二维数组?**
没有一行搞定的方法。得先复制外层,再逐行 `Arrays.copyOf` 每个内层数组。这叫"深拷贝",第 27 章会展开。
**Q:二维数组和 List<List<Integer>> 哪个好?**
看场景。数组性能更好、内存更紧凑;List 更灵活、API 更丰富。固定大小的数据用数组,频繁增删的用 List。