1.Java 数组的基本概念
数组在 Java 中是用来存储相同类型数据的集合,可以容纳多个元素,并通过索引(位置编号)来访问和管理这些元素。数组的特点是长度固定 且只能存储相同类型的元素。在 Java 中,数组分为两种类型:
- 基本类型数组 :用于存储原始数据类型的值(如
int
、double
、char
等)。 - 引用类型数组 :用于存储对象或类实例的引用(如
String
、自定义类等)。
1.1.数组的结构和内存分配
在 Java 中,数组是一个对象,所有数组都派生自 java.lang.Object
。这意味着数组本身在堆(Heap)中分配内存,数组对象包含一个指向数据的引用。在数组中,数据元素是连续存储的,因此可以通过索引快速访问每个元素。
1.2.数组的声明与初始化
数组的声明、创建和初始化步骤包括以下几种方式:
- 声明:指定数组的数据类型和数组名称。
- 创建 :通过
new
关键字分配内存并定义数组长度。 - 初始化:为数组的各个位置赋初始值,可以使用字面量初始化或通过遍历初始化。
示例代码:
java
// 1. 声明和创建基本类型数组
int[] numbers = new int[5]; // 声明并创建一个长度为5的int类型数组
// 2. 直接初始化
int[] numbers = {1, 2, 3, 4, 5}; // 声明并初始化数组元素
// 3. 声明和创建引用类型数组
String[] names = new String[3]; // 声明并创建一个长度为3的String数组
names[0] = "Alice"; // 初始化第一个元素
注 :数组的大小在创建时确定,并且在 Java 中数组一旦创建,长度就不可更改。如果需要动态增删元素,可以考虑使用 ArrayList
等集合类。
1.3.数组的默认值
在 Java 中,数组在创建后会自动初始化各个元素为类型默认值:
- 基本数据类型:
byte
、short
、int
、long
默认值为0
float
和double
默认值为0.0
char
默认值为\u0000
(空字符)boolean
默认值为false
- 引用类型:默认值为
null
示例代码:
java
int[] numbers = new int[3]; // 初始化为 {0, 0, 0}
String[] names = new String[3]; // 初始化为 {null, null, null}
2.基本类型数组
基本类型数组用于存储原始数据类型的值,比如 int
、double
、boolean
等。Java 中的八种基本数据类型是:byte
、short
、int
、long
、float
、double
、char
和 boolean
。这些类型的数组只能存储与类型一致的元素,并且创建时自动分配内存和默认值。
2.1 基本类型数组的声明、创建和初始化
基本类型数组的定义分为以下几个步骤:
- 声明:指定数组的类型和名称。
- 创建 :使用
new
关键字分配内存空间并指定数组长度。 - 初始化:为数组元素赋值,可以通过直接赋值或遍历赋值。
代码示例
java
// 声明一个int类型数组
int[] numbers;
// 创建一个长度为5的int数组
numbers = new int[5];
// 初始化数组元素
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;
// 也可以在声明时直接初始化
int[] numbers = {1, 2, 3, 4, 5}; // 数组字面量初始化
2.2 基本类型数组的默认值
在 Java 中,数组创建后会自动初始化每个元素为对应类型的默认值。这些默认值是:
byte
、short
、int
、long
类型的默认值为0
float
和double
的默认值为0.0
char
类型的默认值为\u0000
(空字符)boolean
类型的默认值为false
代码示例
java
// 声明并创建一个int数组,长度为3
int[] numbers = new int[3]; // 默认值为 {0, 0, 0}
// 声明并创建一个boolean数组,长度为2
boolean[] flags = new boolean[2]; // 默认值为 {false, false}
2.3 基本类型数组的访问和修改
可以使用索引来访问和修改数组元素的值。索引从 0
开始到 数组长度 - 1
。如果访问超过范围的索引,会抛出 ArrayIndexOutOfBoundsException
。
代码示例
java
int[] numbers = {1, 2, 3, 4, 5};
// 访问数组元素
int firstElement = numbers[0]; // 获取第一个元素,值为1
// 修改数组元素
numbers[0] = 10; // 修改第一个元素,值改为10
2.4 基本类型数组的遍历
可以使用 for
循环或增强的 for-each
循环来遍历基本类型数组:
代码示例
java
int[] numbers = {1, 2, 3, 4, 5};
// 使用for循环遍历
for (int i = 0; i < numbers.length; i++) {
System.out.println(numbers[i]);
}
// 使用增强for循环遍历
for (int num : numbers) {
System.out.println(num);
}
2.5 基本类型数组的特点和适用场景
- 存储的数据是连续的:基本类型数组在内存中是连续存储的,访问速度较快。
- 不支持动态大小:基本类型数组的长度固定,无法在运行时更改大小。
- 直接存储数据:基本类型数组存储的是实际的数值而非引用,访问效率高。
- 不能与
Arrays.asList()
直接兼容 :由于自动装箱问题,基本类型数组无法直接转换为List
,可以考虑使用包装类数组或集合类如ArrayList
。
场景示例
当需要固定长度的数值集合且性能要求较高时,适合使用基本类型数组。例如,存储大量传感器读数、计算分数、简单排序或统计等场景。
3.引用类型数组
引用类型数组用于存储对象或类实例的引用,例如 String
、自定义类等。与基本类型数组不同,引用类型数组存储的是对象的引用(地址)而非对象本身。这类数组在存储和访问上稍微复杂一些,因为每个数组元素都是一个对象引用,访问时需要特别注意引用的初始化。
3.1 引用类型数组的声明、创建和初始化
引用类型数组的定义流程与基本类型数组类似,包含声明、创建和初始化步骤。
- 声明:指定引用类型和数组名称。
- 创建 :通过
new
关键字分配内存并指定数组的长度。 - 初始化:为数组的每个位置分配对象引用,可以在声明时直接初始化。
代码示例
java
// 声明并创建一个 String 类型的引用数组
String[] names = new String[3];
// 初始化数组元素
names[0] = "Alice";
names[1] = "Bob";
names[2] = "Charlie";
// 声明并初始化数组
String[] names = {"Alice", "Bob", "Charlie"};
可以存储任意类的对象,因此可以定义自定义类的数组:
java
// 定义一个自定义类
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
// 创建并初始化自定义类对象的数组
Person[] people = new Person[2];
people[0] = new Person("Alice", 30);
people[1] = new Person("Bob", 25);
3.2 引用类型数组的默认值
引用类型数组在创建时,各元素的默认值为 null
,因为引用类型数组存储的是对象的引用,而此时并未创建对象。如果未初始化引用类型数组中的元素,访问时会产生 NullPointerException
。
代码示例
java
// 声明并创建一个 String 类型的引用数组,长度为3
String[] names = new String[3]; // 默认值为 {null, null, null}
3.3 引用类型数组的访问和修改
与基本类型数组一样,可以通过索引访问和修改引用类型数组的元素。需要特别注意的是,引用类型数组的元素是对象的引用,修改数组的某个元素指向新的对象并不会影响其他元素。
代码示例
java
String[] names = {"Alice", "Bob", "Charlie"};
// 访问数组元素
System.out.println(names[0]); // 输出 "Alice"
// 修改数组元素引用
names[0] = "David"; // 将第一个元素的引用更改为新的字符串 "David"
3.4 引用类型数组的遍历
引用类型数组的遍历方式与基本类型数组相同,可以使用 for
循环或增强的 for-each
循环:
代码示例
java
String[] names = {"Alice", "Bob", "Charlie"};
// 使用for循环遍历
for (int i = 0; i < names.length; i++) {
System.out.println(names[i]);
}
// 使用增强for循环遍历
for (String name : names) {
System.out.println(name);
}
3.5 引用类型数组的特点和适用场景
- 存储对象的引用:引用类型数组的每个元素存储的是对象的引用而非对象本身。
- 默认值为
null
:创建引用类型数组后,所有元素的默认值均为null
,需谨慎处理。 - 支持任何类类型 :可以是 Java 自带类如
String
的数组,也可以是自定义类的数组,具有较高的灵活性。 - 可以与
Arrays.asList()
直接兼容 :引用类型数组可以直接转换为List
,因为它们不是基本数据类型。
场景示例
引用类型数组适用于存储对象的集合,例如保存一组 String
值(如学生名单)或自定义类实例(如学生信息、订单等)。
4.多维数组
Java 支持多维数组,最常用的是二维数组,不过 Java 中理论上可以创建任意维度的数组。多维数组是数组的数组,例如,二维数组中的每个元素也是一个数组。与一维数组不同,多维数组可以用来表示表格、矩阵等结构。
4.1 二维数组的声明、创建和初始化
二维数组的创建步骤与一维数组类似,包含声明、创建和初始化步骤:
- 声明:指定数组的类型和名称,同时使用两组方括号表示二维。
- 创建 :通过
new
关键字分配内存空间,并定义每个维度的大小。 - 初始化:可以在声明时直接初始化,或者通过嵌套循环初始化。
代码示例
java
// 声明并创建一个 2行3列 的二维 int 数组
int[][] matrix = new int[2][3];
// 通过初始化设置每个元素的值
matrix[0][0] = 1;
matrix[0][1] = 2;
matrix[0][2] = 3;
matrix[1][0] = 4;
matrix[1][1] = 5;
matrix[1][2] = 6;
// 也可以直接声明并初始化
int[][] matrix = {
{1, 2, 3},
{4, 5, 6}
};
4.2 不规则(锯齿形)二维数组
在 Java 中,二维数组不要求每一行的列数相同,这种结构称为不规则或锯齿形数组。可以在声明时指定行数,再为每一行分配不同的列数。
代码示例
java
// 创建一个 3行 的不规则二维数组
int[][] jaggedArray = new int[3][];
// 为每一行分配不同的列数
jaggedArray[0] = new int[2]; // 第一行有2列
jaggedArray[1] = new int[3]; // 第二行有3列
jaggedArray[2] = new int[1]; // 第三行有1列
// 初始化各个元素
jaggedArray[0][0] = 1;
jaggedArray[0][1] = 2;
// 依此类推
4.3 二维数组的访问和遍历
二维数组的访问需要使用两个索引,第一个索引表示行,第二个索引表示列。可以使用嵌套的 for
循环来遍历二维数组:
代码示例
java
int[][] matrix = {
{1, 2, 3},
{4, 5, 6}
};
// 使用嵌套的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();
}
// 使用增强for循环遍历二维数组
for (int[] row : matrix) {
for (int num : row) {
System.out.print(num + " ");
}
System.out.println();
}
4.4 二维数组的默认值
二维数组在声明后,数组元素会自动初始化为默认值:
- 对于基本类型的二维数组,元素的默认值与一维数组一致(如
0
或false
)。 - 对于引用类型的二维数组,元素的默认值为
null
。
代码示例
java
int[][] numbers = new int[2][3]; // 默认值为 {{0, 0, 0}, {0, 0, 0}}
String[][] names = new String[2][2]; // 默认值为 {{null, null}, {null, null}}
4.5 二维数组的应用场景
- 矩阵计算:如数学中的矩阵运算。
- 表格数据:可以用二维数组来存储表格或图形数据,例如棋盘、电子表格。
- 图形图像处理:二维数组常用于存储图像像素或图形的点阵信息。
- 不规则数据结构:通过不规则数组,可以轻松构建不同宽度的"表格"结构。
5.java.util.Arrays
类的常用方法
java.util.Arrays
是 Java 提供的一个数组操作工具类,包含多种静态方法,可以帮助开发者快速地对数组进行排序、查找、转换、复制等操作。以下是 Arrays
类的主要方法及其详细使用说明:
5.1 Arrays.asList()
作用 :将引用类型数组转换为 List
。
注意 :asList()
只适用于引用类型数组(如 String[]
、Integer[]
),不适用于基本类型数组(如 int[]
、double[]
)。如果传入基本类型数组,则会将整个数组作为 List
的一个元素,而不是逐个元素转换。
代码示例
java
// 引用类型数组转换为List
String[] strArray = {"apple", "banana", "orange"};
List<String> list = Arrays.asList(strArray); // 返回固定大小的 List
// 基本类型数组转换的问题
int[] intArray = {1, 2, 3};
List<int[]> intList = Arrays.asList(intArray); // 错误,整个数组成为单个元素
- 返回结果 :
Arrays.asList()
返回的是一个固定大小的List
,无法增加或删除元素,若尝试增删操作会抛出UnsupportedOperationException
。
5.2 Arrays.sort()
作用 :对数组进行排序。Arrays.sort()
支持对基本类型和引用类型数组进行排序,可以按自然顺序排序,也支持自定义排序规则。
- 基本类型数组排序:按升序排序。
- 引用类型数组排序 :需要元素类型实现
Comparable
接口,按自然顺序排序。
代码示例
java
// 基本类型数组排序
int[] numbers = {4, 2, 5, 1};
Arrays.sort(numbers); // 升序排序,结果为 {1, 2, 4, 5}
// 引用类型数组排序(按字母顺序)
String[] names = {"Charlie", "Alice", "Bob"};
Arrays.sort(names); // 结果为 {"Alice", "Bob", "Charlie"}
// 自定义排序(按降序排序)
Arrays.sort(names, Comparator.reverseOrder());
- 时间复杂度 :
Arrays.sort()
基于双轴快速排序(Dual-Pivot Quicksort)算法,时间复杂度为O(n log n)
。
5.3 Arrays.binarySearch()
作用 :在已排序的数组中查找指定元素的索引。
注意 :binarySearch()
只能在已排序的数组上使用,若数组未排序,结果可能不正确。
- 返回值 :如果找到元素,返回其索引;如果找不到,返回负数。返回值为
-(插入点) - 1
,其中"插入点"是元素应当插入的位置索引。
代码示例
java
int[] numbers = {1, 3, 5, 7, 9};
int index = Arrays.binarySearch(numbers, 5); // 返回2(元素5的位置)
int notFoundIndex = Arrays.binarySearch(numbers, 6); // 返回-4,表示元素6应插入在索引3
- 适用场景:快速查找数组中的元素索引,适合用于频繁查找操作的场景。
5.4 Arrays.copyOf()
和 Arrays.copyOfRange()
作用:创建数组的副本,或复制数组的某个范围。
Arrays.copyOf(array, newLength)
:复制整个数组,或根据指定长度创建截断或扩展的数组。Arrays.copyOfRange(array, from, to)
:复制数组的某一范围,包括起始索引,不包括终止索引。
代码示例
java
int[] original = {1, 2, 3, 4, 5};
// 复制整个数组
int[] copyArray = Arrays.copyOf(original, original.length); // {1, 2, 3, 4, 5}
// 复制前3个元素
int[] partialCopy = Arrays.copyOf(original, 3); // {1, 2, 3}
// 复制索引1到3的元素
int[] rangeCopy = Arrays.copyOfRange(original, 1, 4); // {2, 3, 4}
5.5 Arrays.equals()
和 Arrays.deepEquals()
作用:比较两个数组是否相等。
equals()
:用于比较一维数组是否相等,对比的是每个元素的值。deepEquals()
:适合用于多维数组,比较的是多维数组中的每个子数组是否相等。
代码示例
java
int[] array1 = {1, 2, 3};
int[] array2 = {1, 2, 3};
boolean isEqual = Arrays.equals(array1, array2); // 返回true
int[][] matrix1 = {{1, 2}, {3, 4}};
int[][] matrix2 = {{1, 2}, {3, 4}};
boolean isDeepEqual = Arrays.deepEquals(matrix1, matrix2); // 返回true
5.6 Arrays.fill()
作用:将数组的所有元素都填充为指定的值。适用于基本类型数组和引用类型数组。
代码示例
java
int[] numbers = new int[5];
Arrays.fill(numbers, 1); // 将所有元素设置为1,结果为 {1, 1, 1, 1, 1}
String[] names = new String[3];
Arrays.fill(names, "Unknown"); // 将所有元素设置为 "Unknown"
- 适用场景:用于数组的初始化或重置操作。
5.7 Arrays.toString()
和 Arrays.deepToString()
作用 :将数组转换为字符串形式,一维数组用 toString()
,多维数组用 deepToString()
。
代码示例
java
int[] numbers = {1, 2, 3};
System.out.println(Arrays.toString(numbers)); // 输出: [1, 2, 3]
int[][] matrix = {{1, 2}, {3, 4}};
System.out.println(Arrays.deepToString(matrix)); // 输出: [[1, 2], [3, 4]]
- 适用场景:调试时用于查看数组的内容。
5.8 Arrays.hashCode()
和 Arrays.deepHashCode()
作用:计算数组的哈希码,适用于一维数组和多维数组的哈希计算。
hashCode()
:用于计算一维数组的哈希码。deepHashCode()
:用于计算多维数组的哈希码。
代码示例
java
int[] numbers = {1, 2, 3};
int hash = Arrays.hashCode(numbers); // 计算一维数组的哈希码
int[][] matrix = {{1, 2}, {3, 4}};
int deepHash = Arrays.deepHashCode(matrix); // 计算多维数组的哈希码
5.9 Arrays.parallelSort()
作用 :对数组进行并行排序(适用于大数组,提升性能)。与 sort()
不同,parallelSort()
使用了多线程技术,在多核处理器上性能更高。
代码示例
java
int[] numbers = {5, 3, 8, 2};
Arrays.parallelSort(numbers); // 并行排序
- 适用场景:处理大规模数据时,可以提高排序效率。
5.10 Arrays.stream()
作用 :将数组转换为 Stream
对象,便于使用流式操作。对于对象数组(如 String[]
),会转换为 Stream<T>
,而基本类型数组(如 int[]
)会转换为对应的 IntStream
、LongStream
或 DoubleStream
。
代码示例
java
int[] numbers = {1, 2, 3, 4, 5};
IntStream stream = Arrays.stream(numbers); // 使用stream操作
stream.forEach(System.out::println);