多维数组
白歌:"很多人把多维数组想象成一个矩阵表格,这在概念上没错,但在 Java 的内存模型中,它其实是'数组的数组'。这一层理解,决定了你是否能写出不规则数组,也决定了你面试时能否画对内存图。"
多维数组的本质
在 Java 中,多维数组的本质是"数组的数组"(Array of Arrays)。这意味着:
- 二维数组并不是一个真正的"表格",而是一个一维数组,这个数组的每个元素又是指向另一个一维数组的引用
- 每一行的长度可以不同(不规则数组 / 锯齿数组)
- 每一行在堆内存中是独立分配的,行与行之间地址不一定连续
关键认知:图中每一行长度都不相同——这正是 Java 二维数组的不规则性。外层数组存储的是引用,指向各自独立的内层数组,每行内存不连续。
二维数组的定义与初始化
声明语法
| 声明方式 | 语法 | 说明 |
|---|---|---|
| 标准声明 | int[][] matrix; | 推荐,清晰表达"int 的二维数组" |
| C 风格 | int matrix[][]; | 兼容 C 语法,不推荐 |
| 混合声明陷阱 | int[] matrix[]; | 合法但令人困惑,不推荐 |
静态初始化
// 方式一:完整静态初始化(推荐)
int[][] matrix = {
{85, 92, 78, 95}, // 第0行:4个学生
{76, 88, 90}, // 第1行:3个学生
{93, 83, 87, 69, 91} // 第2行:5个学生
};
// 方式二:使用 new + 匿名数组(声明与初始化分离时)
int[][] matrix;
matrix = new int[][]{
{85, 92, 78},
{76, 88, 90}
};
动态初始化
// 方式一:指定行数和列数(矩形二维数组)
int[][] matrix = new int[3][4]; // 3行4列,所有元素默认值0
// 方式二:只指定行数,列数稍后确定(不规则数组)
int[][] matrix = new int[3][]; // 只创建外层数组,3个行引用均为null
matrix[0] = new int[4]; // 第0行:4列
matrix[1] = new int[3]; // 第1行:3列
matrix[2] = new int[5]; // 第2行:5列
不规则数组(锯齿数组 / Ragged Array)
这是 Java 多维数组区别于 C/C++ 的一个重要特性。由于每一行是独立分配的数组对象,它们可以有不同长度。
遍历不规则二维数组
int[][] matrix = {
{85, 92, 78, 95},
{76, 88, 90},
{93, 83, 87, 69, 91}
};
// 标准 for 循环遍历
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) { // 注意:每行用各自的 length
System.out.print(matrix[i][j] + "\t");
}
System.out.println();
}
// 增强 for 循环遍历(更简洁)
for (int[] row : matrix) { // 外层:每次取一行(int[])
for (int val : row) { // 内层:遍历该行的每个元素
System.out.print(val + "\t");
}
System.out.println();
}
三维数组概念
三维数组可以理解为"数组的数组的数组"——外层一维数组的每个元素是一个二维数组引用。
// 三维数组:飞翔科技仓库管理 —— [仓库编号][货架编号][商品编号]
int[][][] warehouse = new int[2][3][4];
// 2个仓库,每个仓库3个货架,每个货架4个商品位
// 静态初始化三维数组
int[][][] warehouse = {
{ // 仓库0
{101, 102, 103, 104}, // 货架0
{201, 202, 203, 204}, // 货架1
{301, 302, 303, 304} // 货架2
},
{ // 仓库1
{111, 112, 113, 114}, // 货架0
{211, 212, 213, 214}, // 货架1
{311, 312, 313, 314} // 货架2
}
};
完整代码示例
示例一:飞翔科技班级成绩表(不规则数组)
场景:飞翔科技的在线教育平台中,每个班级的学生人数不同。小崔需要用一个不规则二维数组存储三个班级的 Java 成绩,并计算每个班级的平均分和全年级总平均分。
/**
* 飞翔科技 —— 班级成绩管理系统
* 演示不规则二维数组的定义、遍历和统计分析
*/
public class ClassScoreAnalyzer {
public static void main(String[] args) {
// 三个班级,每个班级人数不同
int[][] classScores = {
{85, 92, 78, 95, 88}, // 1班:5人(计算机科学)
{76, 88, 90}, // 2班:3人(软件工程)
{93, 83, 87, 69, 91, 77} // 3班:6人(人工智能)
};
String[] classNames = {"计算机科学1班", "软件工程2班", "人工智能3班"};
System.out.println("========== 飞翔科技 各班级Java成绩分析 ==========\n");
double schoolTotal = 0.0;
int schoolCount = 0;
// 遍历每个班级
for (int i = 0; i < classScores.length; i++) {
System.out.println(classNames[i] + "(共" + classScores[i].length + "人):");
int classSum = 0;
int classMax = classScores[i][0];
int classMin = classScores[i][0];
// 遍历该班级的每位学生
for (int j = 0; j < classScores[i].length; j++) {
int score = classScores[i][j];
System.out.printf(" 学号 %d:%d 分\n", j + 1, score);
classSum += score;
if (score > classMax) classMax = score;
if (score < classMin) classMin = score;
}
double classAvg = (double) classSum / classScores[i].length;
System.out.printf(" 班级总分:%d | 平均分:%.2f | 最高:%d | 最低:%d\n\n",
classSum, classAvg, classMax, classMin);
schoolTotal += classSum;
schoolCount += classScores[i].length;
}
// 全年级统计
double schoolAvg = schoolTotal / schoolCount;
System.out.println("========== 全年级汇总 ==========");
System.out.printf("总人数:%d\n", schoolCount);
System.out.printf("全年级总分:%.0f\n", schoolTotal);
System.out.printf("全年级平均分:%.2f\n", schoolAvg);
}
}
运行输出:
========== 飞翔科技 各班级Java成绩分析 ==========
计算机科学1班(共5人):
学号 1:85 分
学号 2:92 分
学号 3:78 分
学号 4:95 分
学号 5:88 分
班级总分:438 | 平均分:87.60 | 最高:95 | 最低:78
软件工程2班(共3人):
学号 1:76 分
学号 2:88 分
学号 3:90 分
班级总分:254 | 平均分:84.67 | 最高:90 | 最低:76
人工智能3班(共6人):
学号 1:93 分
学号 2:83 分
学号 3:87 分
学号 4:69 分
学号 5:91 分
学号 6:77 分
班级总分:500 | 平均分:83.33 | 最高:93 | 最低:69
========== 全年级汇总 ==========
总人数:14
全年级总分:1192
全年级平均分:85.14
示例二:飞翔科技仓库三维库存管理
场景:飞翔科技自研的智慧仓储系统中,需要用一个三维数组管理仓库 → 货架 → 商品位的库存信息。大翔让小崔实现库存盘点功能。
/**
* 飞翔科技 —— 三维仓库库存管理系统
* 演示三维数组的定义、遍历和条件搜索
*/
public class WarehouseInventory {
public static void main(String[] args) {
// 三维数组:[2个仓库][3个货架][4个商品位]
// 存储每个商品位的商品编号(0表示空位)
int[][][] warehouse = {
{ // 仓库 A(电子产品仓)
{1001, 1002, 0, 1004}, // 货架A-0:4个商品位
{2001, 0, 2003, 2004}, // 货架A-1
{0, 3002, 3003, 0} // 货架A-2
},
{ // 仓库 B(办公用品仓)
{4001, 0, 4003, 0}, // 货架B-0
{5001, 5002, 5003, 5004}, // 货架B-1
{6001, 0, 6003, 6004} // 货架B-2
}
};
String[] warehouseNames = {"电子产品仓(A)", "办公用品仓(B)"};
System.out.println("========== 飞翔科技智慧仓储盘点报告 ==========\n");
int totalSlots = 0;
int occupiedSlots = 0;
int emptySlots = 0;
// 遍历三维数组
for (int w = 0; w < warehouse.length; w++) {
System.out.println(warehouseNames[w] + ":");
int warehouseOccupied = 0;
int warehouseEmpty = 0;
for (int s = 0; s < warehouse[w].length; s++) {
System.out.printf(" 货架 %d:", s);
int shelfOccupied = 0;
for (int p = 0; p < warehouse[w][s].length; p++) {
int productId = warehouse[w][s][p];
totalSlots++;
if (productId == 0) {
warehouseEmpty++;
shelfOccupied += 0;
} else {
warehouseOccupied++;
shelfOccupied++;
System.out.printf("[商品%d] ", productId);
}
}
System.out.printf("(占用 %d/%d 位)\n",
shelfOccupied, warehouse[w][s].length);
}
System.out.printf(" 仓库占用率:%.1f%% (%d/%d)\n\n",
100.0 * warehouseOccupied /
(warehouseOccupied + warehouseEmpty),
warehouseOccupied,
warehouseOccupied + warehouseEmpty);
}
occupiedSlots = totalSlots - (warehouse[0][0].length * 3 * 2 - occupiedSlots);
// 重新按业务逻辑计算
int totalOccupied = 0;
int totalEmptySlots = 0;
for (int[][] wh : warehouse) {
for (int[] shelf : wh) {
for (int product : shelf) {
if (product == 0) {
totalEmptySlots++;
} else {
totalOccupied++;
}
}
}
}
System.out.println("========== 总体统计 ==========");
System.out.printf("总商品位数量:%d\n", totalSlots);
System.out.printf("已占用数量:%d\n", totalOccupied);
System.out.printf("空闲数量:%d\n", totalEmptySlots);
System.out.printf("总占用率:%.1f%%\n", 100.0 * totalOccupied / totalSlots);
}
}
运行输出:
========== 飞翔科技智慧仓储盘点报告 ==========
电子产品仓(A):
货架 0:[商品1001] [商品1002] [商品1004] (占用 3/4 位)
货架 1:[商品2001] [商品2003] [商品2004] (占用 3/4 位)
货架 2:[商品3002] [商品3003] (占用 2/4 位)
仓库占用率:66.7% (8/12)
办公用品仓(B):
货架 0:[商品4001] [商品4003] (占用 2/4 位)
货架 1:[商品5001] [商品5002] [商品5003] [商品5004] (占用 4/4 位)
货架 2:[商品6001] [商品6003] [商品6004] (占用 3/4 位)
仓库占用率:75.0% (9/12)
========== 总体统计 ==========
总商品位数量:24
已占用数量:17
空闲数量:7
总占用率:70.8%
易错场景
反例一:遍历不规则数组时错用统一列长
小崔写了一段代码遍历成绩表,结果抛出了异常:
// ❌ 错误:假设所有行的列数相同
int[][] matrix = {
{85, 92, 78},
{76, 88, 90, 95}, // 这行有4个元素
{93, 83}
};
int cols = matrix[0].length; // 3 —— 错误!
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < cols; j++) { // 用统一列数遍历
System.out.print(matrix[i][j] + " "); // i=1, j=3 时越界!
}
System.out.println();
}
// ✅ 正确:每行用各自的 length
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) { // 各行用自己的长度
System.out.print(matrix[i][j] + " ");
}
System.out.println();
}
反例二:只 new 了外层数组就访问内层
// ❌ 错误:只创建了外层引用数组,内层数组全是 null
int[][] matrix = new int[3][]; // matrix[0]、matrix[1]、matrix[2] 都是 null
matrix[0][0] = 100; // NullPointerException!
// ✅ 正确:先创建每一行
int[][] matrix = new int[3][];
for (int i = 0; i < matrix.length; i++) {
matrix[i] = new int[4]; // 为每一行分配数组对象
}
matrix[0][0] = 100; // 现在安全了
反例三:三维数组的 length 理解错误
int[][][] cube = new int[2][3][4];
System.out.println(cube.length); // 2 —— 最外层长度(仓库数)
System.out.println(cube[0].length); // 3 —— 第二层长度(货架数)
System.out.println(cube[0][0].length); // 4 —— 最内层长度(商品位数)
// cube[0] 是一个 int[][](二维数组)
// cube[0][0] 是一个 int[](一维数组)
// cube[0][0][0] 是一个 int(基本类型值)
面试考点
Q1:Java 的二维数组在内存中真的是连续的一块吗?
不是。Java 二维数组是"数组的数组"——外层数组的元素是引用,指向各自独立的内层数组。每一行在堆内存中是独立分配的对象,行与行之间地址不一定连续。这与 C/C++ 中
int matrix[3][4]的连续内存模型有本质区别。
Q2:不规则数组(锯齿数组)在什么场景下使用?
当数据天然不规则时使用,例如:
- 每个班级学生人数不同
- 每个省份下辖城市数量不同
- 文本处理中每行字符数不同
不规则数组可以节省内存(不为空位分配空间),但遍历时必须以
matrix[i].length为准,不能假设统一列数。
Q3:int[][] arr = new int[3][4] 在内存中创建了几个对象?
4 个对象:1 个外层数组对象(长度为3,存储
int[]引用)+ 3 个内层数组对象(每个长度为4,存储int值)。总计 1 + 3 = 4 个数组对象。每个对象都有各自的对象头、length 字段和元素数据区。
Q4:int[][] arr = new int[3][] 创建了几个对象?
1 个对象:仅创建了外层数组对象(长度为3,存储
int[]引用,初始值均为null)。3 个内层数组尚未创建——此时arr[0]、arr[1]、arr[2]都是null,一旦访问会抛出NullPointerException。
Q5:如何遍历不规则二维数组的所有元素?
外层循环以
matrix.length为上界,内层循环以matrix[i].length为上界:for (int i = 0; i < matrix.length; i++) { for (int j = 0; j < matrix[i].length; j++) { // 处理 matrix[i][j] } }或使用增强 for 循环:
for (int[] row : matrix) { for (int val : row) { // 处理 val } }
白歌的提醒:"理解'数组的数组'是掌握 Java 多维数组的关键。在面试中画出二维数组的内存图——外层引用数组指向各行独立的内层数组——往往能让你在众多候选人中脱颖而出。另外注意,三维及更高维度的数组在实战中较少出现,如果遇到,优先考虑用类封装而不是多层数组嵌套。"