java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java多重数组

Java多重数组使用及说明

作者:心之语歌

本文介绍了Java中多维数组的定义、声明、初始化、访问、遍历以及内存结构,文章还讨论了Java多维数组与C/C++的区别,以及如何处理锯齿数组和深拷贝,此外,还提到了数组与链表的区别,包括它们的内存布局、操作效率和适用场景

概念

在 Java 中,“多重数组”通常指的是多维数组(Multidimensional Arrays),最常见的是二维数组(如矩阵),但也可以有三维、四维等。

多重数组概念

Java 的多重数组本质上是 “数组的数组”(array of arrays),即:

Java 的多维数组不要求每行长度相同(称为“锯齿数组”或“不规则数组”)。

声明与初始化

1. 声明

int[][] matrix;          // 声明一个二维整型数组
String[][][] data;       // 三维字符串数组

2. 初始化方式

方式一:直接字面量初始化(推荐用于小数据)

int[][] grid = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

方式二:指定大小(规则矩形)

int rows = 3, cols = 4;
int[][] mat = new int[rows][cols];  // 3x4 的全 0 矩阵

方式三:锯齿数组(Jagged Array)— 每行长度不同

int[][] jagged = new int[3][];      // 3 行,列数未定
jagged[0] = new int[2];             // 第0行有2列
jagged[1] = new int[5];             // 第1行有5列
jagged[2] = new int[1];             // 第2行有1列

注意:new int[3][4] 是规则数组;new int[3][] 是锯齿数组(需手动初始化每行)。

访问与修改元素

int value = matrix[i][j];     // 读取第 i 行第 j 列
matrix[i][j] = 100;           // 修改

边界检查:Java 会在运行时自动检查下标是否越界(抛出 ArrayIndexOutOfBoundsException)。

遍历多重数组

1. 普通 for 循环(知道行列数)

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();
}

2. 增强 for 循环(for-each,简洁安全)

for (int[] row : matrix) {
    for (int val : row) {
        System.out.print(val + " ");
    }
    System.out.println();
}

推荐:优先使用 for-each,避免下标错误,尤其处理锯齿数组时更安全。

内存结构(重要理解)

Java 的二维数组不是一块连续内存(不像 C/C++),而是:

matrix → [ ref0, ref1, ref2 ]
           ↓     ↓     ↓
         [1,2] [3,4,5] [6]

优势:灵活;劣势:缓存局部性较差(相比连续内存)

常见使用场景(面试高频)

场景示例
矩阵操作旋转矩阵、螺旋遍历、对角线遍历
动态规划二维 DP 表(如 LCS、编辑距离)
图的邻接矩阵graph[i][j] == 1 表示 i→j 有边
棋盘/地图模拟迷宫、岛屿数量、生命游戏
分组存储每行代表一类数据

实用工具方法

1. 获取行列数

int rows = matrix.length;
int cols = matrix[0].length;  // 注意:仅当至少有一行且非空时安全!

安全写法(处理空数组):

if (matrix == null || matrix.length == 0) return;
int rows = matrix.length;
int cols = matrix[0].length; // 此时可安全访问

2. 深拷贝二维数组(避免引用共享)

int[][] deep = new int[original.length][];
for (int i = 0; i < original.length; i++) {
    deep[i] = original[i].clone(); // 克隆每一行
}
类型含义特点
浅拷贝(Shallow Copy)复制对象的引用,不复制内部对象新旧数组共享子对象
深拷贝(Deep Copy)递归复制所有层级的对象完全独立,互不影响
数组类型浅拷贝是否安全?如何实现深拷贝
int[], double[] 等基本类型✅ 安全(值拷贝)arr.clone() 即可
String[]⚠️ 表面安全(String 不可变)通常 clone() 足够
int[][](二维基本类型)❌ 不安全循环 + row.clone()
Object[](含可变对象)❌ 不安全需手动深拷贝每个元素(可能递归)

注意:String 虽是引用类型,但不可变(immutable),所以浅拷贝通常不会出问题。但如果是 StringBuilder[],就必须深拷贝!

误区正确理解
“clone() 就是深拷贝”❌ 默认是浅拷贝,除非重写 clone() 方法
“Arrays.copyOf() 是深拷贝”❌ 对多维数组仍是浅拷贝
“基本类型数组不需要深拷贝”✅ 正确!因为存的是值,不是引用

常见陷阱 & 注意事项

问题说明
空指针异常matrix 为 null,或某行为 null(锯齿数组未初始化)
列数不一致误以为所有行长度相同,直接用 matrix[0].length 遍历所有行
浅拷贝问题直接赋值导致修改副本影响原数组
内存浪费用二维数组存稀疏矩阵(此时应考虑 Map<Pair, Value> 或稀疏表示)

数组跟链表

数组(Array)和链表(Linked List)是两种最基础、最重要的线性数据结构。它们在内存布局、操作效率、适用场景上有本质区别。掌握它们的差异,是算法设计和系统优化的关键。

特性数组(Array)链表(Linked List)
内存布局连续内存块非连续,靠指针连接
随机访问✅ O(1)(通过下标)❌ O(n)(必须遍历)
插入/删除(中间)❌ O(n)(需移动元素)✅ O(1)(已知节点时)
插入/删除(头部)❌ O(n)(除非用特殊技巧)✅ O(1)
插入/删除(尾部)✅ O(1)(动态数组均摊)✅ O(1)(若有 tail 指针)
空间开销仅数据本身每个节点额外存指针(如 next)
缓存友好性✅ 高(局部性好)❌ 低(内存跳跃)
大小固定?静态数组固定;动态数组可扩容动态伸缩,无需预分配
场景推荐结构原因
需要频繁随机访问(如排序、DP)✅ 数组O(1) 访问
频繁在头部/中间插入删除✅ 链表O(1) 修改指针
元素数量固定或可预估✅ 数组内存紧凑,性能高
实现栈(只操作尾部)✅ 数组 or 链表两者都 O(1)
实现队列(首尾操作)✅ 链表 or 循环数组链表天然支持;数组需循环缓冲
内存敏感(小对象大量存储)✅ 数组链表指针开销大
需要缓存友好(高性能计算)✅ 数组连续内存,预取高效

扩展:高维数组(了解即可)

// 三维数组:3 层,每层 4 行,每行 5 列
int[][][] cube = new int[3][4][5];

// 访问
cube[0][1][2] = 10;

// 遍历
for (int[][] layer : cube) {
    for (int[] row : layer) {
        for (int val : row) {
            // ...
        }
    }
}

实际开发中,三维以上数组较少见,通常用对象封装更清晰。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文