数组是 Java 中用于存储固定大小、相同类型元素集合的基础数据结构。
一、核心特性
-
固定长度
创建后长度不可变,需在声明时指定:
int[] arr = new int[5];
内存分配:连续存储空间(堆内存) -
索引访问
元素通过从 0 开始的索引访问:
arr[0] = 10;
索引范围:[0, length-1] -
类型约束
所有元素必须是同一数据类型(基本类型或对象引用)
二、声明与初始化
| 方式 | 示例代码 | 说明 |
|---|---|---|
| 先声明后初始化 | int[] arr;arr = new int[3]; | 默认值:数值型为 0,布尔型为 false |
| 声明时初始化 | int[] arr = {1, 2, 3}; | 编译器自动推导长度 |
| 动态初始化 | String[] names = new String[]{"A", "B"}; | 允许重新赋值 |
//动态初始化 包含默认初始化
//使用new操作符来创建数组,语法:
dataType[] arrayRefVar = new dataType[arraySize]
int[] a = new int[10]
//静态初始化 创建+赋值
int[] a = {1,2,3};
Man[] men ={new Man(1,1),new Man(2,2)};
三、关键操作
-
遍历数组
int[] arr = new int[10]; // 标准 for 循环 for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } // 增强 for 循环(Java 5+) 快捷键:array.for+enter for (int num : arr) { System.out.println(num); } -
多维数组
// 二维数组声明 int[][] matrix = new int[3][4]; int[][] array = {{1,2},{3,4},{5,6}}; // 不规则数组 int[][] jagged = {{1}, {2,3}, {4,5,6}};
二维数组介绍
在Java中,二维数组是一种数据结构,用于存储表格状的数据(如矩阵)。它本质上是“数组的数组”,即每个元素本身是一个一维数组。二维数组的行数和列数可以灵活定义,但索引必须从0开始。
1. 二维数组的声明和初始化
声明二维数组时,需指定数据类型和维度。基本语法如下:
声明:
数据类型[][] 数组名;初始化:可以通过
new关键字分配内存,或直接赋初值。常见初始化方式:
- 固定大小初始化:
数组名 = new 数据类型[行数][列数];- 直接赋初值:
数据类型[][] 数组名 = {{值1, 值2, ...}, {值3, 值4, ...}, ...};示例:
// 声明并初始化一个2行3列的整数数组 int[][] matrix = new int[2][3]; // 所有元素初始化为0 // 直接赋初值 int[][] matrix2 = {{1, 2, 3}, {4, 5, 6}}; // 2行3列
2. 访问和修改元素
二维数组的元素通过行索引i和列索引j访问,索引从0开始。例如:
访问元素:
数组名[i][j]修改元素:
数组名[i][j] = 新值;数学上,元素位置表示为arr[i][j],其中i是行索引(范围0到行数-1),j是列索引(范围0到列数-1)。
示例代码:int[][] arr = {{10, 20}, {30, 40}}; int element = arr[0][1]; // 访问第一行第二列元素,结果为20 arr[1][0] = 50; // 修改第二行第一列元素为50
3. 常见操作
遍历二维数组
使用嵌套循环遍历所有元素:
外层循环遍历行,内层循环遍历列。
获取数组大小:
数组名.length获取行数。数组名[i].length获取第i行的列数(支持锯齿状数组)。示例代码:
int[][] matrix = {{1, 2, 3}, {4, 5}}; // 锯齿状数组:第一行3列,第二行2列 for (int i = 0; i < matrix.length; i++) { // i从0到行数-1 for (int j = 0; j < matrix[i].length; j++) { // j从0到当前行列数-1 System.out.print(matrix[i][j] + " "); } System.out.println(); // 换行 } // 输出: // 1 2 3 // 4 5
其他操作
- 获取元素总数:遍历计算所有元素个数。
- 复制数组:使用
System.arraycopy()或循环手动复制。- 动态调整大小:Java二维数组大小固定,需创建新数组实现“扩容”。
4. 完整示例程序
以下是一个完整的Java程序,演示二维数组的声明、初始化、遍历和修改:
public class TwoDArrayExample { public static void main(String[] args) { // 初始化一个3行2列的字符串数组 String[][] names = new String[3][2]; names[0][0] = "Alice"; names[0][1] = "Bob"; names[1][0] = "Charlie"; names[1][1] = "Diana"; names[2][0] = "Eve"; names[2][1] = "Frank"; // 遍历并打印所有元素 System.out.println("二维数组内容:"); for (int i = 0; i < names.length; i++) { for (int j = 0; j < names[i].length; j++) { System.out.print(names[i][j] + " "); } System.out.println(); // 换行 } // 修改一个元素 names[1][1] = "David"; System.out.println("修改后第二行第二列: " + names[1][1]); } }
运行此程序输出:
二维数组内容: Alice Bob Charlie Diana Eve Frank 修改后第二行第二列: David
3. 数组工具类-Array类
Arrays类是Java集合框架的核心工具类(位于java.util包),提供静态方法操作数组,涵盖排序、搜索、比较等常见操作。
1. 数组排序
- 方法:
sort() - 支持类型:所有基本数据类型及对象数组
- 底层实现:双轴快速排序(基本类型) / TimSort(对象类型)
int[] nums = {5, 2, 9, 1};
Arrays.sort(nums); // 结果: [1, 2, 5, 9]
2. 二分查找
- 方法:
binarySearch() - 前提:数组必须已排序
- 返回值:找到返回索引,否则返回负数
int[] sorted = {1, 3, 5, 7};
int index = Arrays.binarySearch(sorted, 5); // 返回 2
int notFound = Arrays.binarySearch(sorted, 4); // 返回 -3
3. 数组填充
- 方法:
fill() - 作用:将指定值填充到数组所有/部分位置
char[] chars = new char[5];
Arrays.fill(chars, 'A'); // 结果: ['A','A','A','A','A']
Arrays.fill(chars, 1, 3, 'B'); // 部分填充: ['A','B','B','A','A']
4. 数组比较
- 方法:
equals()/deepEquals() - 区别:
equals():比较一维数组内容deepEquals():递归比较多维数组
int[] a = {1, 2}, b = {1, 2};
boolean isEqual = Arrays.equals(a, b); // true
int[][] matrix1 = {{1,2}, {3,4}};
int[][] matrix2 = {{1,2}, {3,4}};
boolean deepEqual = Arrays.deepEquals(matrix1, matrix2); // true
5. 数组转列表
- 方法:
asList() - 注意:返回的列表是固定长度(不可增删)
String[] names = {"Alice", "Bob"};
List<String> list = Arrays.asList(names); // 固定大小列表
list.set(0, "Carol"); // 允许修改
// list.add("Dave"); // 抛出 UnsupportedOperationException
6. 数组复制
- 方法:
copyOf()/copyOfRange() - 特点:自动处理新数组长度(截断或补默认值)
int[] origin = {10, 20, 30};
int[] copy1 = Arrays.copyOf(origin, 2); // [10, 20]
int[] copy2 = Arrays.copyOf(origin, 5); // [10,20,30,0,0]
int[] rangeCopy = Arrays.copyOfRange(origin, 1, 3); // [20, 30]
7. 数组转字符串
- 方法:
toString()/deepToString() - 作用:快速输出可读的数组内容
int[] arr = {1, 2, 3};
System.out.println(Arrays.toString(arr)); // 输出: [1, 2, 3]
int[][] matrix = {{1,2}, {3,4}};
System.out.println(Arrays.deepToString(matrix)); // 输出: [[1, 2], [3, 4]]
关键特性总结
- 静态工具类:无需实例化,直接通过类名调用
- 泛型支持:对对象数组操作时自动推断类型
- 性能优化:排序/搜索等方法针对不同场景高度优化
- 空安全:方法内部处理
null输入(如toString(null)返回"null")
最佳实践:优先使用
Arrays而非手动实现数组操作,可提升代码健壮性和可读性。
import java.util.Arrays;
// 排序
Arrays.sort(arr);
// 二分查找(需先排序)
int index = Arrays.binarySearch(arr, 5);
// 快速填充
Arrays.fill(arr, -1);
具体完整方法可查看Java官方文档。
四、内存分析
Java内存分析是优化应用程序性能和避免内存问题的关键过程。它涉及理解Java虚拟机(JVM)的内存管理机制,包括内存区域划分、对象生命周期和垃圾回收(Garbage Collection, GC)
步骤1: Java内存模型
JVM将内存划分为几个主要区域,每个区域有特定用途:
- 堆(Heap):存储所有对象实例和数组。堆是GC的主要工作区,分为新生代(Young Generation)和老年代(Old Generation)。新生代又包括Eden区、Survivor区(S0和S1)。堆的大小可通过JVM参数设置,例如初始堆大小用Xms表示,最大堆大小用Xmx表示。
- 栈(Stack):每个线程私有,存储局部变量、方法调用和基本类型数据。栈帧(Stack Frame)随方法调用入栈,方法结束出栈。栈溢出常见于递归过深。
- 方法区(Method Area):存储类信息、常量、静态变量等。在Java 8后,通常由元空间(Metaspace)实现,使用本地内存。
- 程序计数器(PC Register):线程私有,指示当前执行指令的地址。
- 本地方法栈(Native Method Stack):支持本地方法(如C/C++代码)调用。
内存分析的核心是监控堆的使用,因为对象创建和GC直接影响性能。例如,堆使用率超过阈值可能触发Full GC,导致应用暂停。
步骤2: 使用内存分析工具
Java提供多种工具来可视化内存使用,帮助诊断问题:
- JConsole:内置工具,监控堆、线程、GC活动。启动命令:
jconsole。 - VisualVM:更强大,支持内存快照(Heap Dump)分析。可安装插件如MAT(Memory Analyzer Tool)。
- 命令行工具:如
jstat监控GC统计,jmap生成堆转储文件。 - 第三方工具:如Eclipse MAT或YourKit,用于深度分析内存泄漏。
使用步骤:
- 运行Java应用时添加JVM参数:
-XX:+HeapDumpOnOutOfMemoryError,以便在内存溢出时自动生成堆转储。 - 启动VisualVM,连接到目标进程,查看“Monitor”标签下的内存图表。
- 分析堆转储:识别大对象或无效引用链。
步骤3: 实际示例与代码分析
以下是一个简单Java程序,演示常见内存问题(如内存泄漏)。
import java.util.ArrayList;
import java.util.List;
public class MemoryLeakExample {
private static List<Object> leakList = new ArrayList<>(); // 静态集合可能导致内存泄漏
public static void main(String[] args) {
while (true) {
Object obj = new Object(); // 创建新对象
leakList.add(obj); // 对象被静态集合引用,无法被GC回收
// 模拟业务逻辑
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 内存分析:
- 堆使用:程序运行后,堆内存持续增长,因为
leakList持有对象引用,阻止GC回收。使用JConsole观察,堆曲线呈上升趋势,最终导致OutOfMemoryError。 - 问题诊断:静态集合
leakList是根因,应改为弱引用(如WeakReference)或及时清理。 - 优化建议:避免长生命周期对象引用短生命周期对象;使用
null显式解除引用。
- 堆使用:程序运行后,堆内存持续增长,因为
五、应用场景
- 高频访问:需要O(1)时间复杂度随机访问
- 固定数据集:如棋盘状态、常量表
- 算法基础:排序/查找算法的底层实现
六、代码实例
冒泡排序
package Array;
public class TestSort {
static void main(String[] args) {
int[] array = {5,7,3,7,2,9};
sort(array);
printArray(array);
}
public static void sort(int[] array){
int temp = 0;
for (int i = 0; i < array.length - 1; i++) {
boolean flag = false;
for (int j = 0; j < array.length - 1 - i; j++) {
if (array[j] > array[j + 1]) {
temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
flag = true;
}
}
if (!flag) break;
}
}
public static void printArray(int[] array){
for (int i = 0; i < array.length; i++) {
if (i == 0){
System.out.print("[");
}
if (i == array.length-1){
System.out.print(array[i]+"]");
}else
System.out.print(array[i]+", ");
}
}
}
七、稀疏数组
稀疏数组是一种高效的数据结构,用于存储具有大量零元素(或默认值)的矩阵。它通过只记录非零元素的位置和值来节省内存空间。在Java中,稀疏数组常用于处理大规模稀疏数据,如图像处理、科学计算和机器学习领域。下面我将逐步解释其原理、实现方法和应用。
1. 稀疏数组的概念
-
为什么需要稀疏数组?
在标准二维数组中,如果一个矩阵中非零元素很少(例如,只有10%的元素非零),存储所有元素会浪费大量内存。稀疏数组只存储非零元素,形式为三元组:(行号, 列号, 值)。例如,一个矩阵 A 中,非零元素 a_{ij} 存储为 (i, j, a_{ij}) -
应用场景
- 图像处理:存储黑白图像中的像素点(非零值代表黑色)。
- 网络图:表示图的邻接矩阵(非零值代表连接)。
- 科学数据:处理大型矩阵中的稀疏数据集。
2. Java实现稀疏数组
在Java中,稀疏数组可以通过自定义类实现,核心是使用列表(如ArrayList)存储三元组。每个三元组包含行索引、列索引和值。以下是关键步骤:
- 数据结构设计:
- 定义一个类来封装三元组。
- 添加方法:设置值(set)、获取值(get)、转换回标准数组。
- 优势与局限:
- 优点:节省内存;支持高效插入和查询非零元素。
- 缺点:随机访问速度较慢(需遍历列表);不适合高密度数据。
下面是一个完整的Java代码示例。
需求:编写五子棋游戏中,有存盘退出和续上盘的功能
package Array;
import java.util.Arrays;
public class ArrayDome06 {
static void main(String[] args) {
int[][] array1 = new int[11][11];
array1[1][2] = 1;
array1[2][3] = 2;
//输出原始数组
for (int i = 0; i < array1.length; i++) {
for (int j = 0; j < array1[i].length; j++) {
System.out.print(array1[i][j]+"\t");
}
System.out.println();
}
System.out.println("================");
//转换成稀疏数组
int sum = 0;
for (int i = 0; i < array1.length; i++) {
for (int j = 0; j < array1[i].length; j++) {
if (array1[i][j] != 0){
sum++;
}
}
}
int[][] array2 = new int[sum+1][3];
array2[0][0] = 11;
array2[0][1] = 11;
array2[0][2] = sum;
int count = 1;
for (int i = 0; i < array1.length; i++) {
for (int j = 0; j < array1[i].length; j++) {
if (array1[i][j] != 0){
array2[count][0] = i;
array2[count][1] = j;
array2[count][2] = array1[i][j];
count++;
}
}
}
for (int i = 0; i < array2.length; i++) {
System.out.println(array2[i][0]+"\t"
+array2[i][1]+"\t"
+array2[i][2]);
}
//还原为原数组
System.out.println("=============");
int[][] array3 = new int[array2[0][0]][array2[0][1]];
for (int i = 1; i < array2.length; i++) {
array3[array2[i][0]][array2[i][1]] = array2[i][2];
}
for (int[] ints : array3) {
for (int ints1 : ints) {
System.out.print(ints1+"\t");
}
System.out.println();
}
}
}
代码结果:
0 0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0 0
0 0 0 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
================
11 11 2
1 2 1
2 3 2
=============
0 0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0 0
0 0 0 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
八、注意事项
- 数组越界:访问
arr[arr.length]抛出ArrayIndexOutOfBoundsException - 长度限制:最大长度受
Integer.MAX_VALUE - 5限制 - 对象数组:存储的是对象引用(非对象本身)
最佳实践:当需要动态扩容时,优先使用
ArrayList(基于数组实现)
1074

被折叠的 条评论
为什么被折叠?



