Java数组是存储同类型元素的固定大小容器,是Java编程中最基础的数据结构之一。其核心原理基于连续内存分配和索引访问,适用于需要高效随机访问的场景(如排序、查找)。本教程将覆盖数组的原理、声明初始化、常见操作、多维数组及性能优化,帮助你快速掌握数组的使用技巧。
一、数组的核心原理
1. 内存分配机制
Java数组的内存分配遵循“栈存引用,堆存数据”的原则:
-
栈内存:存储数组的引用变量(如
int[] arr;中的arr),占用固定空间(通常为4字节)。 -
堆内存:存储数组的实际元素(如
new int[5]创建的5个int元素),占用连续空间(int类型每个元素占4字节,总大小为5×4=20字节)。 -
引用关联:栈中的引用变量指向堆中的数组实例(如
arr = new int[5];后,arr指向堆中的5个连续int空间)。
示例:
int[] arr = new int[3]; // 栈中arr指向堆中的3个连续int空间(默认值均为0)
2. 索引机制
数组通过索引(从0开始)访问元素,索引范围为0到length-1(length是数组的长度,由new关键字指定)。例如:
arr[0] = 1; // 给第一个元素赋值(索引0)
arr[1] = 2; // 给第二个元素赋值(索引1)
arr[2] = 3; // 给第三个元素赋值(索引2)
System.out.println(arr[1]); // 输出2(访问索引1的元素)
3. 固定大小特性
数组一旦创建,长度不可改变(如new int[5]的数组无法扩展为6个元素)。若需动态大小,可使用ArrayList(基于数组实现的动态数组)。
二、数组的声明与初始化
1. 声明数组
声明数组需要指定数据类型和数组名,语法有两种(推荐第一种,可读性更好):
数据类型[] 数组名; // 推荐(如int[] arr;)
数据类型 数组名[]; // 不推荐(如int arr[];)
2. 分配内存
声明后,需用new关键字分配内存(指定数组长度),语法为:
数组名 = new 数据类型[数组长度];
示例:
int[] arr; // 声明int类型数组
arr = new int[5]; // 分配5个int元素的内存(默认值均为0)
3. 初始化数组
初始化是为数组元素赋值,有两种方式:
-
静态初始化(声明时直接赋值):
数据类型[] 数组名 = {值1, 值2, ..., 值n};示例:
int[] arr = {1, 2, 3, 4, 5}; // 声明并初始化5个int元素 -
动态初始化(声明后逐个赋值):
数组名[索引] = 值;示例:
int[] arr = new int[5]; arr[0] = 1; arr[1] = 2; arr[2] = 3; arr[3] = 4; arr[4] = 5;
4. 多维数组(以二维为例)
多维数组是数组的数组,语法为:
-
声明:
数据类型[][] 数组名;(如int[][] matrix;) -
分配内存:
数组名 = new 数据类型[行数][列数];(如matrix = new int[3][4];,表示3行4列的二维数组) -
初始化:
-
静态初始化:
int[][] matrix = {{1,2,3}, {4,5,6}, {7,8,9}};(3行3列) -
动态初始化:
matrix[0][0] = 1; matrix[0][1] = 2; ...(逐个赋值)
-
示例:
int[][] matrix = new int[3][4]; // 3行4列的二维数组(默认值均为0)
matrix[0][0] = 1; // 第一行第一列赋值为1
matrix[1][2] = 2; // 第二行第三列赋值为2
三、数组的常见操作
1. 遍历数组
遍历是访问数组所有元素的常用操作,有两种方式:
-
普通for循环(可访问索引):
for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); // 输出索引i的元素 } -
增强for循环(无法访问索引,语法更简洁):
for (int num : arr) { System.out.println(num); // 输出每个元素 }
2. 获取数组长度
数组的length属性返回数组的长度(固定值),语法为:
int len = arr.length; // 如arr={1,2,3},len=3
3. 复制数组
复制数组需避免引用赋值(如int[] arr2 = arr;,此时arr2和arr指向同一堆内存,修改arr2会影响arr)。推荐以下方法:
-
System.arraycopy()(原生方法,性能最优):语法:
System.arraycopy(源数组, 源起始索引, 目标数组, 目标起始索引, 复制长度);示例:
int[] arr1 = {1,2,3,4,5}; int[] arr2 = new int[5]; System.arraycopy(arr1, 0, arr2, 0, arr1.length); // 将arr1复制到arr2 -
Arrays.copyOf()(简化复制,需导入java.util.Arrays):语法:
数据类型[] 新数组 = Arrays.copyOf(原数组, 新长度);示例:
int[] arr1 = {1,2,3}; int[] arr2 = Arrays.copyOf(arr1, 5); // 复制arr1到arr2(长度扩展为5,新增元素为默认值0) -
clone()方法(对象数组的复制):语法:
数据类型[] 新数组 = 原数组.clone();示例:
String[] str1 = {"a", "b", "c"}; String[] str2 = str1.clone(); // 复制str1到str2
4. 排序数组
Java提供了Arrays.sort()方法(需导入java.util.Arrays),支持升序排序(默认)和自定义排序(通过Comparator接口)。
-
基本类型数组排序:
int[] arr = {5, 3, 1, 4, 2}; Arrays.sort(arr); // 升序排序(结果:1,2,3,4,5) -
对象数组排序(如
String数组按字典序排序):String[] str = {"banana", "apple", "cherry"}; Arrays.sort(str); // 升序排序(结果:apple, banana, cherry) -
自定义排序(如
Student对象按年龄排序):class Student { int age; String name; public Student(int age, String name) { this.age = age; this.name = name; } } Student[] students = {new Student(20, "Alice"), new Student(18, "Bob")}; Arrays.sort(students, new Comparator<Student>() { @Override public int compare(Student s1, Student s2) { return s1.age - s2.age; // 按年龄升序排序 } });
四、多维数组的高级应用
1. 不规则数组(锯齿数组)
多维数组的每行长度可以不同(如int[][] jagged = new int[3][];,第一行2个元素,第二行3个元素,第三行1个元素)。
示例:
int[][] jagged = new int[3][];
jagged[0] = new int[2]; // 第一行2个元素
jagged[1] = new int[3]; // 第二行3个元素
jagged[2] = new int[1]; // 第三行1个元素
jagged[0][0] = 1;
jagged[1][1] = 2;
jagged[2][0] = 3;
2. 多维数组的遍历
多维数组需用嵌套循环遍历(外层循环遍历行,内层循环遍历列)。
示例(二维数组):
int[][] matrix = {{1,2,3}, {4,5,6}, {7,8,9}};
for (int i = 0; i < matrix.length; i++) { // 遍历行(3行)
for (int j = 0; j < matrix[i].length; j++) { // 遍历列(每行3列)
System.out.print(matrix[i][j] + " "); // 输出:1 2 3 4 5 6 7 8 9
}
System.out.println(); // 换行
}
五、数组的性能优化策略
1. 使用基本类型数组
基本类型数组(如int[])比对象数组(如Integer[])更快且占用空间更少(避免了对象的装箱/拆箱开销)。
示例:
int[] intArr = new int[1000000]; // 占用4MB(1000000×4字节)
Integer[] integerArr = new Integer[1000000]; // 占用16MB(1000000×16字节,对象头+引用)
2. 避免频繁数组扩展
数组长度固定,频繁扩展(如Arrays.copyOf())会导致内存重新分配和数据复制,影响性能。若需动态大小,推荐使用ArrayList(内部使用数组,自动扩展,减少复制次数)。
示例:
// 不推荐(频繁扩展)
int[] arr = new int[10];
for (int i = 0; i < 1000; i++) {
if (i >= arr.length) {
arr = Arrays.copyOf(arr, arr.length * 2); // 扩展为原来的2倍
}
arr[i] = i;
}
// 推荐(使用ArrayList)
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add(i); // 自动扩展,无需手动复制
}
3. 优化循环结构
-
减少循环中的计算:将不变的计算移出循环(如
int len = arr.length; for (int i = 0; i < len; i++),避免每次循环都调用arr.length)。 -
使用增强for循环:比普通for循环更简洁,且避免索引越界错误(如
for (int num : arr))。 -
并行流处理(Java 8+):对于大规模数组,使用并行流(
parallelStream())利用多核处理器加速处理(如Arrays.stream(arr).parallel().forEach(num -> {...}))。
4. 预分配足够大的数组
若已知数组的大致大小,可预分配足够大的空间(如new int[1000]),避免频繁扩展。例如,读取文件时,可先获取文件大小,再预分配数组。
六、数组的常见错误与解决方法
1. 索引越界异常(ArrayIndexOutOfBoundsException)
原因:访问数组的索引超出了0到length-1的范围(如arr[5],而arr.length=5)。
解决方法:
-
循环时使用
arr.length作为边界(如for (int i = 0; i < arr.length; i++))。 -
避免硬编码索引(如
arr[3],若数组长度变化,会导致错误)。
2. 空指针异常(NullPointerException)
原因:数组引用变量为null(如int[] arr = null; arr[0] = 1;)。
解决方法:
-
声明数组后及时分配内存(如
arr = new int[5];)。 -
检查数组引用是否为
null(如if (arr != null) { ... })。
3. 数组复制错误(引用赋值)
原因:使用=赋值数组(如int[] arr2 = arr1;),此时arr2和arr1指向同一堆内存,修改arr2会影响arr1。
解决方法:使用System.arraycopy()、Arrays.copyOf()或clone()方法复制数组(如int[] arr2 = Arrays.copyOf(arr1, arr1.length);)。
七、总结
Java数组是固定大小的同类型元素容器,核心原理是连续内存分配和索引访问。其关键知识点包括:
-
声明初始化:
数据类型[] 数组名 = new 数据类型[长度];(动态)或{值1, 值2, ...}(静态)。 -
常见操作:遍历(
for/增强for)、长度(length)、复制(System.arraycopy()/Arrays.copyOf())、排序(Arrays.sort())。 -
多维数组:
数据类型[][] 数组名 = new 数据类型[行数][列数];(规则)或new 数据类型[行数][];(不规则)。 -
性能优化:使用基本类型数组、避免频繁扩展、优化循环结构、预分配足够空间。
通过本教程,你已掌握数组的核心原理与实战技巧。在实际开发中,需根据场景需求选择合适的数据结构(如动态大小用ArrayList,高性能随机访问用数组),并遵循性能优化策略,提升代码效率。
726

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



