一、数组概述
数组是Java编程中最基础且重要的数据结构之一,它是相同类型数据的有序集合。数组在内存中以连续的方式存储,这种存储结构使得数组具有高效的随机访问特性。
1.1 数组的基本概念
数组(Array)是由相同类型的元素(element)组成的数据结构,这些元素在内存中按照线性顺序排列。每个元素都可以通过数组索引(index)来访问,索引从0开始计数。
数组的主要特点包括:
-
固定长度:数组一旦创建,其长度就不可改变
-
类型一致:所有元素必须是相同的数据类型
-
内存连续:元素在内存中是连续存储的
-
高效访问:通过索引可以直接访问任意元素,时间复杂度为O(1)
1.2 数组的应用场景
数组在编程中有着广泛的应用:
-
存储大量相同类型的数据
-
实现矩阵、表格等数据结构
-
作为其他复杂数据结构的基础(如堆、栈、队列等)
-
算法实现(排序、搜索等)
二、数组的声明与初始化
2.1 数组的声明
在Java中,数组的声明有以下几种方式:
java
// 方式一:数据类型[] 数组名;
int[] arr1;
// 方式二:数据类型 数组名[];
int arr2[];
推荐使用第一种方式,因为它更清楚地表明这是一个"int数组"类型。
2.2 数组的初始化
数组初始化分为静态初始化和动态初始化两种方式。
2.2.1 静态初始化
静态初始化是指在声明数组的同时直接指定元素内容:
java
// 完整格式
int[] arr1 = new int[]{1, 2, 3, 4, 5};
// 简化格式
int[] arr2 = {1, 2, 3, 4, 5};
// 字符串数组示例
String[] names = {"Alice", "Bob", "Charlie"};
2.2.2 动态初始化
动态初始化是指只指定数组长度,由系统为数组元素分配初始值:
java
// 格式:数据类型[] 数组名 = new 数据类型[数组长度];
int[] arr = new int[5]; // 创建一个长度为5的int数组,元素初始值为0
// 其他类型的默认值:
// boolean数组默认值为false
// 引用类型数组默认值为null
// double数组默认值为0.0
2.3 数组的默认值
当数组动态初始化时,系统会为数组元素分配默认值:
-
整数类型(byte, short, int, long):0
-
浮点类型(float, double):0.0
-
字符类型(char):'\u0000'(空字符)
-
布尔类型(boolean):false
-
引用类型:null
三、数组的基本操作
3.1 访问数组元素
数组元素通过索引访问,索引范围从0到数组长度-1:
java
int[] numbers = {10, 20, 30, 40, 50};
// 访问第一个元素
System.out.println(numbers[0]); // 输出10
// 修改第三个元素
numbers[2] = 33;
// 遍历数组
for (int i = 0; i < numbers.length; i++) {
System.out.println(numbers[i]);
}
3.2 数组长度
数组的length属性表示数组的长度:
java
int[] arr = new int[10];
System.out.println(arr.length); // 输出10
注意:length是属性而不是方法,不要加括号。
3.3 数组遍历
常见的数组遍历方式有以下几种:
3.3.1 普通for循环
java
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
3.3.2 增强for循环(foreach)
java
for (int num : arr) {
System.out.println(num);
}
3.3.3 使用Arrays.toString()
java
System.out.println(Arrays.toString(arr));
3.4 数组越界问题
Java会对数组访问进行边界检查,如果访问超出范围的索引,会抛出ArrayIndexOutOfBoundsException:
java
int[] arr = {1, 2, 3};
System.out.println(arr[3]); // 运行时错误:ArrayIndexOutOfBoundsException
四、多维数组
Java支持多维数组,最常见的是二维数组。
4.1 二维数组的声明与初始化
4.1.1 静态初始化
java
// 标准形式
int[][] matrix1 = new int[][]{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// 简化形式
int[][] matrix2 = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
4.1.2 动态初始化
java
// 规则二维数组
int[][] matrix3 = new int[3][4]; // 3行4列
// 不规则二维数组
int[][] matrix4 = new int[3][];
matrix4[0] = new int[2];
matrix4[1] = new int[3];
matrix4[2] = new int[4];
4.2 二维数组的访问与遍历
java
// 访问特定元素
int value = matrix[1][2];
// 遍历二维数组
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();
}
// 使用增强for循环遍历
for (int[] row : matrix) {
for (int num : row) {
System.out.print(num + " ");
}
System.out.println();
}
4.3 多维数组的内存模型
在Java中,多维数组实际上是"数组的数组"。例如,二维数组可以看作是一个一维数组,其中每个元素又是一个一维数组。
五、数组的常用操作与工具类
5.1 数组拷贝
5.1.1 System.arraycopy()
java
int[] src = {1, 2, 3, 4, 5};
int[] dest = new int[5];
// 参数:源数组, 源起始位置, 目标数组, 目标起始位置, 拷贝长度
System.arraycopy(src, 0, dest, 0, src.length);
5.1.2 Arrays.copyOf()
java
int[] original = {1, 2, 3, 4, 5};
int[] copy = Arrays.copyOf(original, original.length);
// 可以指定新长度,多余位置补默认值
int[] longerCopy = Arrays.copyOf(original, 10);
5.2 数组排序
java
int[] numbers = {5, 3, 9, 1, 6};
// 升序排序
Arrays.sort(numbers);
// 部分排序
Arrays.sort(numbers, 1, 4); // 对索引1到3的元素排序
// 自定义排序(需实现Comparator接口)
Integer[] nums = {5, 3, 9, 1, 6};
Arrays.sort(nums, (a, b) -> b - a); // 降序排序
5.3 数组搜索
java
int[] sortedArr = {1, 3, 5, 7, 9};
// 二分查找(数组必须已排序)
int index = Arrays.binarySearch(sortedArr, 5); // 返回2
// 如果元素不存在,返回-(插入点)-1
int notFound = Arrays.binarySearch(sortedArr, 4); // 返回-3
5.4 数组比较
java
int[] arr1 = {1, 2, 3};
int[] arr2 = {1, 2, 3};
int[] arr3 = {1, 3, 2};
System.out.println(Arrays.equals(arr1, arr2)); // true
System.out.println(Arrays.equals(arr1, arr3)); // false
5.5 数组填充
java
int[] arr = new int[5];
// 填充整个数组
Arrays.fill(arr, 10); // [10, 10, 10, 10, 10]
// 填充部分数组
Arrays.fill(arr, 1, 3, 20); // [10, 20, 20, 10, 10]
5.6 数组转列表
java
String[] names = {"Alice", "Bob", "Charlie"};
// 返回的List是Arrays的内部类,不支持add/remove操作
List<String> nameList = Arrays.asList(names);
// 如果需要可修改的List
List<String> modifiableList = new ArrayList<>(Arrays.asList(names));
六、数组与方法的交互
6.1 数组作为方法参数
java
public static void printArray(int[] arr) {
for (int num : arr) {
System.out.print(num + " ");
}
System.out.println();
}
// 调用
int[] numbers = {1, 2, 3};
printArray(numbers);
6.2 数组作为返回值
java
public static int[] reverseArray(int[] arr) {
int[] result = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
result[arr.length - 1 - i] = arr[i];
}
return result;
}
// 调用
int[] original = {1, 2, 3, 4, 5};
int[] reversed = reverseArray(original);
6.3 可变参数
可变参数本质上就是数组,但提供了更灵活的调用方式:
java
public static int sum(int... numbers) {
int total = 0;
for (int num : numbers) {
total += num;
}
return total;
}
// 调用方式
System.out.println(sum(1, 2, 3)); // 输出6
System.out.println(sum(1, 2, 3, 4, 5)); // 输出15
System.out.println(sum(new int[]{1, 2, 3})); // 也可以直接传入数组
七、数组的高级特性
7.1 数组与泛型
Java不允许直接创建泛型数组:
java
// 以下代码编译错误
// List<String>[] arrayOfLists = new List<String>[10];
// 变通方案:使用原始类型或通配符类型
List<?>[] arrayOfLists = new List<?>[10];
7.2 数组与集合的转换
java
// 数组转List
String[] names = {"Alice", "Bob", "Charlie"};
List<String> nameList = Arrays.asList(names); // 固定大小List
List<String> modifiableList = new ArrayList<>(Arrays.asList(names));
// List转数组
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
String[] array1 = list.toArray(new String[0]); // 推荐方式
String[] array2 = list.toArray(new String[list.size()]);
7.3 数组的性能考虑
数组在某些场景下比集合类更高效:
-
内存占用更小(没有额外的对象开销)
-
访问速度更快(直接内存访问)
-
更适合固定大小的数据结构
但数组也有局限性:
-
长度固定
-
功能有限(需要手动实现排序、搜索等操作)
-
不支持泛型
八、常见数组算法
8.1 数组反转
java
public static void reverse(int[] arr) {
for (int i = 0; i < arr.length / 2; i++) {
int temp = arr[i];
arr[i] = arr[arr.length - 1 - i];
arr[arr.length - 1 - i] = temp;
}
}
8.2 查找最大值/最小值
java
public static int findMax(int[] arr) {
int max = arr[0];
for (int num : arr) {
if (num > max) {
max = num;
}
}
return max;
}
public static int findMin(int[] arr) {
int min = arr[0];
for (int num : arr) {
if (num < min) {
min = num;
}
}
return min;
}
8.3 数组求和与平均值
java
public static int sum(int[] arr) {
int total = 0;
for (int num : arr) {
total += num;
}
return total;
}
public static double average(int[] arr) {
return (double) sum(arr) / arr.length;
}
8.4 数组合并
java
public static int[] mergeArrays(int[] arr1, int[] arr2) {
int[] result = new int[arr1.length + arr2.length];
System.arraycopy(arr1, 0, result, 0, arr1.length);
System.arraycopy(arr2, 0, result, arr1.length, arr2.length);
return result;
}
8.5 数组元素去重
java
public static int[] removeDuplicates(int[] arr) {
if (arr.length == 0) return arr;
Arrays.sort(arr);
int uniqueCount = 1;
for (int i = 1; i < arr.length; i++) {
if (arr[i] != arr[i-1]) {
uniqueCount++;
}
}
int[] result = new int[uniqueCount];
result[0] = arr[0];
int index = 1;
for (int i = 1; i < arr.length; i++) {
if (arr[i] != arr[i-1]) {
result[index++] = arr[i];
}
}
return result;
}
九、数组的常见问题与解决方案
9.1 数组越界问题
问题:访问不存在的索引位置
解决方案:始终检查索引范围,确保在0到length-1之间
java
int[] arr = {1, 2, 3};
int index = 3;
if (index >= 0 && index < arr.length) {
System.out.println(arr[index]);
} else {
System.out.println("Invalid index");
}
9.2 空指针异常
问题:数组未初始化就使用
解决方案:在使用前检查数组是否为null
java
int[] arr = null;
if (arr != null) {
System.out.println(arr.length);
} else {
System.out.println("Array is null");
}
9.3 数组长度不可变
问题:数组创建后长度固定
解决方案:需要扩展数组时,创建新数组并拷贝数据
java
int[] original = {1, 2, 3};
int[] larger = Arrays.copyOf(original, original.length * 2);
9.4 多维数组不规则问题
问题:多维数组的子数组长度不一致可能导致错误
解决方案:访问多维数组元素时检查子数组长度
java
int[][] matrix = new int[3][];
matrix[0] = new int[2];
matrix[1] = new int[3];
matrix[2] = new int[4];
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();
}
十、Java 8+中的数组新特性
10.1 使用Stream处理数组
java
int[] numbers = {1, 2, 3, 4, 5};
// 求和
int sum = Arrays.stream(numbers).sum();
// 平均值
OptionalDouble avg = Arrays.stream(numbers).average();
// 过滤
int[] evens = Arrays.stream(numbers).filter(n -> n % 2 == 0).toArray();
// 映射
int[] squares = Arrays.stream(numbers).map(n -> n * n).toArray();
// 排序
int[] sorted = Arrays.stream(numbers).sorted().toArray();
10.2 并行数组处理
java
int[] largeArray = new int[1000000];
// 填充数组...
// 并行排序
Arrays.parallelSort(largeArray);
// 并行计算
int sum = Arrays.stream(largeArray).parallel().sum();
10.3 Arrays新增方法
Java 8之后,Arrays类增加了一些实用方法:
java
// setAll
int[] squares = new int[10];
Arrays.setAll(squares, i -> i * i);
// parallelPrefix (累积计算)
int[] numbers = {1, 2, 3, 4, 5};
Arrays.parallelPrefix(numbers, (a, b) -> a + b);
// 结果:[1, 3, 6, 10, 15]
十一、性能优化与最佳实践
11.1 数组与集合的选择
-
使用数组的场景:
-
元素数量固定且已知
-
需要极致性能
-
基本数据类型存储
-
多维数据结构
-
-
使用集合的场景:
-
需要动态扩容
-
需要丰富的API操作
-
对象类型存储
-
需要线程安全
-
11.2 数组拷贝的性能考虑
-
对于小数组,各种拷贝方法差异不大
-
对于大数组,System.arraycopy()性能最佳
-
Arrays.copyOf()内部使用System.arraycopy(),但更简洁
11.3 多维数组的内存布局
-
Java多维数组是"数组的数组",不保证内存完全连续
-
对于性能关键的多维数组操作,可以考虑使用一维数组模拟:
java
// 二维数组模拟
int rows = 3, cols = 4;
int[] matrix = new int[rows * cols];
// 访问第i行第j列的元素
int value = matrix[i * cols + j];
11.4 避免常见的性能陷阱
-
避免在循环中重复计算数组长度:
java
// 不好 for (int i = 0; i < arr.length; i++) {...} // 更好(对于大数组) int len = arr.length; for (int i = 0; i < len; i++) {...} -
预先分配足够大的数组,避免频繁扩容
-
考虑缓存友好性,尽量顺序访问数组元素
十二、总结
数组作为Java中最基础的数据结构,具有简单、高效的特性。通过本文的学习,我们系统地了解了:
-
数组的基本概念和特点
-
数组的声明、初始化和基本操作
-
多维数组的使用方法
-
数组与工具类的配合使用
-
数组与方法的交互方式
-
数组的高级特性和性能优化
-
常见数组算法和问题解决方案
-
Java新版本中的数组增强功能
掌握数组不仅有助于理解更复杂的数据结构,也是编写高效Java程序的基础。在实际开发中,应根据具体需求合理选择数组或集合类,并注意避免数组常见的问题和性能陷阱。
2607

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



