目录
在Java中,数组是一种可以存储固定大小的相同类型元素的数据结构。数组中的每个元素都可以通过索引(一个非负整数)来访问,这些索引从0开始,直到length-1
。
1 基本概念
数组是一种线性数据结构,它能够存储固定数量的相同类型的元素。在Java中,数组具有以下特性:
-
固定大小:一旦创建,数组的长度(即它可以容纳的元素数量)是固定的。如果你需要一个可以动态改变大小的数据结构,可能需要考虑使用
ArrayList
或其他集合类。 -
相同类型:数组中的所有元素必须是相同的数据类型。这个类型是在声明数组时指定的,并且不能更改。
-
索引访问:数组中的每个元素都可以通过其索引来访问,索引从0开始。例如,在一个有5个元素的数组中,第一个元素位于索引0,最后一个元素位于索引4。
2 数组的定义
在Java中,定义数组通常涉及两个步骤:声明和初始化。下面详细说明这两个步骤:
2.1 声明数组
声明数组意味着告诉编译器你将要使用的数组变量的名称及其所包含的数据类型。语法如下:
dataType[] arrayName;
// 或
dataType arrayName[];
这里 dataType
是数组元素的数据类型,而 arrayName
是数组变量的名字。例子如下:
int[] scores; // 声明了一个整型数组
String[] names; // 声明了一个字符串数组
2.2 初始化数组
初始化数组意味着为数组分配内存并可能设置初始值。这可以通过两种方式完成:
- 动态初始化:使用
new
关键字创建数组,并指定数组的大小。 - 静态初始化:直接用一组值来初始化数组。
2.2.1 动态初始化
当你知道数组的大小但还没有具体的值时,可以使用 new
来创建数组。
arrayName = new dataType[arraySize];
示例代码:
scores = new int[5]; // 创建一个能存放5个整数的数组
names = new String[3]; // 创建一个能存放3个字符串的数组
你动态创建一个数组时,数组的元素会被自动初始化为该类型默认的初始值。这些默认值取决于数组元素的数据类型。以下是各种数据类型的默认值:
-
数值类型:
byte
:0short
:0int
:0long
:0Lfloat
:0.0fdouble
:0.0d
-
布尔类型:
boolean
:false
-
引用类型(包括对象和数组):
null
(表示没有引用任何对象)
示例代码:
public class ArrayInitializationExample {
public static void main(String[] args) {
// 数值类型数组
int[] intArray = new int[5];
System.out.println("int array default values: " + java.util.Arrays.toString(intArray));
long[] longArray = new long[5];
System.out.println("long array default values: " + java.util.Arrays.toString(longArray));
float[] floatArray = new float[5];
System.out.println("float array default values: " + java.util.Arrays.toString(floatArray));
double[] doubleArray = new double[5];
System.out.println("double array default values: " + java.util.Arrays.toString(doubleArray));
// 布尔类型数组
boolean[] booleanArray = new boolean[5];
System.out.println("boolean array default values: " + java.util.Arrays.toString(booleanArray));
// 引用类型数组
String[] stringArray = new String[5];
System.out.println("String array default values: " + java.util.Arrays.toString(stringArray));
}
}
输出结果:
int array default values: [0, 0, 0, 0, 0]
long array default values: [0, 0, 0, 0, 0]
float array default values: [0.0, 0.0, 0.0, 0.0, 0.0]
double array default values: [0.0, 0.0, 0.0, 0.0, 0.0]
boolean array default values: [false, false, false, false, false]
String array default values: [null, null, null, null, null]
在这个例子中,你可以看到每种类型的数组都被初始化为其默认值。对于基本类型的数组,每个元素被初始化为零或false
;而对于引用类型的数组,每个元素被初始化为null
。
2.2.2 静态初始化
如果在创建数组时已经有了具体的值,可以直接用这些值来初始化数组。
dataType[] arrayName = {value0, value1, ..., valueN};
示例代码:
int[] scores = {98, 92, 76, 88, 95}; // 初始化一个整型数组
String[] names = {"Alice", "Bob", "Charlie"}; // 初始化一个字符串数组
在这个例子中,数组的大小由提供的值的数量自动确定,不需要显式地指定。
示例代码:下面是完整的示例代码,展示了如何声明、创建和初始化数组:
public class ArrayExample {
public static void main(String[] args) {
// 声明数组
int[] numbers;
// 使用new关键字创建数组
numbers = new int[5];
// 打印数组长度
System.out.println("数组长度: " + numbers.length);
// 直接初始化数组
int[] values = {1, 2, 3, 4, 5};
// 遍历并打印数组元素
for (int i = 0; i < values.length; i++) {
System.out.println("Element at index " + i + ": " + values[i]);
}
}
}
3 数组常用方法
3.1 获取数组长度
- 方法:
arrayName.length
- 说明: 这不是方法调用,而是访问数组的
length
属性。它返回数组中元素的数量。
int[] numbers = {1, 2, 3, 4, 5};
int length = numbers.length; // length 将是 5
3.2 数组排序
- 方法:
Arrays.sort(array)
- 说明: 使用
java.util.Arrays
类的sort
方法对数组进行排序。
import java.util.Arrays;
int[] numbers = {5, 3, 2, 4, 1};
Arrays.sort(numbers); // 排序后的数组: [1, 2, 3, 4, 5]
3.3 数组复制
- 方法:
Arrays.copyOf(array, newLength)
或System.arraycopy(src, srcPos, dest, destPos, length)
- 说明:
Arrays.copyOf
用于创建一个新数组,并将原数组的内容复制到新数组中。System.arraycopy
用于直接复制数组的一部分或全部内容到另一个数组。
import java.util.Arrays;
int[] original = {1, 2, 3, 4, 5};
int[] copy = Arrays.copyOf(original, original.length); // 创建一个完全相同的副本
// 或者使用 System.arraycopy
int[] anotherCopy = new int[original.length];
// 参数:源数组,源数组中起始位置,目标数组,目标数组起始位置,要复制的数组元素数量
System.arraycopy(original, 0, anotherCopy, 0, original.length);
3.4 数组填充
- 方法:
Arrays.fill(array, value)
或Arrays.fill(array, fromIndex, toIndex, value)
- 说明: 使用指定值填充整个数组或数组的一部分。
import java.util.Arrays;
int[] numbers = new int[5];
Arrays.fill(numbers, 7); // 所有元素都被设置为 7: [7, 7, 7, 7, 7]
Arrays.fill(numbers, 1, 4, 9); // 从索引1到3(不包括4)的元素被设置为 9: [7, 9, 9, 9, 7]
3.5 数组转换为字符串
- 方法:
Arrays.toString(array)
- 说明: 将数组转换成一个包含所有元素的字符串表示形式,方便打印输出。
import java.util.Arrays;
int[] numbers = {1, 2, 3, 4, 5};
String str = Arrays.toString(numbers); // 结果: "[1, 2, 3, 4, 5]"
System.out.println(str);
3.6 数组比较
- 方法:
Arrays.equals(array1, array2)
- 说明: 比较两个数组是否相等(即它们的长度相同且对应位置的元素都相等)。
import java.util.Arrays;
int[] a1 = {1, 2, 3};
int[] a2 = {1, 2, 3};
boolean isEqual = Arrays.equals(a1, a2); // 返回 true
3.7 查找数组中的元素
- 方法:
Arrays.binarySearch(array, key)
或Arrays.binarySearch(array, fromIndex, toIndex, key)
- 说明: 对已排序的数组执行二分查找。如果找到,则返回元素的索引;否则返回负数(插入点的反码)。
import java.util.Arrays;
int[] numbers = {1, 2, 3, 4, 5};
int index = Arrays.binarySearch(numbers, 3); // 返回 2
3.8 数组转列表
- 方法:
Arrays.asList(array)
- 说明: 将数组转换为列表。注意,对于原始类型数组,这不会工作,但对于对象类型的数组是有效的。
- 注意:当你使用
Arrays.asList(strings)
将数组转换为列表时,实际上得到的是一个固定大小的列表。这个列表是Arrays
类内部实现的一个私有静态类,它直接包装了原始数组,因此不支持添加或删除元素的操作。操作时会抛出UnsupportedOperationException
异常。
import java.util.Arrays;
import java.util.List;
String[] strings = {"a", "b", "c"};
List<String> list = Arrays.asList(strings); // 转换为 List
以上转换的列表list是不能添加删除元素的,要解决这个可以使用一下方法之一:
方法1:使用 ArrayList 构造函数
你可以将 Arrays.asList()
返回的列表传递给 ArrayList
的构造函数来创建一个新的可修改的列表。
String[] strings = {"a", "b", "c"};
List<String> list = new ArrayList<>(Arrays.asList(strings)); // 转换为可变 List
System.out.println(list);
list.add("d");
System.out.println(list);
方法2:使用 Collections.addAll()
你也可以先创建一个空的 ArrayList
,然后使用 Collections.addAll()
方法来添加数组中的所有元素。
String[] strings = {"a", "b", "c"};
List<String> list = new ArrayList<>(); // 创建一个新的可变 List
Collections.addAll(list, strings); // 添加数组中的所有元素
System.out.println(list);
list.add("d");
System.out.println(list);
4 多维数组
多维数组是数组的一种扩展形式,它允许你在多个维度上组织数据。在Java中,最常见的是二维数组(也称为矩阵),但也可以有三维或更多维度的数组。下面是对多维数组的详细解释:
4.1 二维数组
4.1.1 声明和创建
二维数组可以看作是“数组的数组”。每个元素本身也是一个数组。
声明:
dataType[][] arrayName;
创建:
arrayName = new dataType[rows][columns];
或者同时声明和创建:
arrayName = new dataType[rows][columns];
4.1.2 初始化
4.1.2.1 动态初始化
int[][] matrix = new int[3][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;
matrix[2][0] = 7;
matrix[2][1] = 8;
matrix[2][2] = 9;
4.1.2.2 静态初始化
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
4.1.3 访问和修改元素
可以通过双重索引访问和修改二维数组中的元素。
int value = matrix[1][2]; // 获取第二行第三列的值 (6)
matrix[1][2] = 60; // 修改第二行第三列的值为 60
可以使用嵌套的 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();
}
4.2 不规则数组
不规则数组是指每一行的长度可以不同的二维数组。这种数组在实际应用中非常常见,例如表示树结构或稀疏矩阵。
4.2.1 声明和创建
dataType[][] arrayName;
arrayName = new dataType[rows][];
4.2.2 初始化
每一行可以有不同的长度。
String[][] jaggedArray = new String[3][];
jaggedArray[0] = new String[2];
jaggedArray[1] = new String[3];
jaggedArray[2] = new String[1];
jaggedArray[0][0] = "A";
jaggedArray[0][1] = "B";
jaggedArray[1][0] = "C";
jaggedArray[1][1] = "D";
jaggedArray[1][2] = "E";
jaggedArray[2][0] = "F";
4.2.3 访问和修改元素
可以通过双重索引访问和修改不规则数组中的元素。
String value = jaggedArray[1][2]; // 获取第二行第三列的值 ("E")
jaggedArray[1][2] = "G"; // 修改第二行第三列的值为 "G"
可以使用嵌套的 for
循环来遍历不规则数组。
for (int i = 0; i < jaggedArray.length; i++) {
for (int j = 0; j < jaggedArray[i].length; j++) {
System.out.print(jaggedArray[i][j] + " ");
}
System.out.println();
}
5 数组在内存中的存储
数组是一种基础的数据结构,用于在内存中连续存储相同类型的数据。了解数组在内存中的存储方式对于优化程序性能、理解垃圾回收机制以及进行内存管理等方面都非常重要。下面是对Java数组在内存中的存储方式的详细分析:
5.1 栈(Stack)与堆(Heap)
-
栈(Stack):
- 栈是用于存储方法调用和局部变量的地方。
- 每当一个方法被调用时,一个新的栈帧(stack frame)被创建,其中包含该方法的局部变量、方法参数和返回地址。
- 栈内存的分配和释放非常快,因为它遵循后进先出(LIFO)的原则。
- 栈内存是线程私有的,每个线程有自己的栈。
-
堆(Heap):
- 堆是所有线程共享的内存区域,用于存储对象实例和数组。
- 使用
new
关键字创建的对象和数组都存储在堆中。 - 堆内存的分配和释放较慢,因为需要进行垃圾回收(Garbage Collection, GC)来管理不再使用的对象。
- 堆内存的大小通常比栈内存大得多,并且可以根据应用程序的需求动态扩展。
5.2 数组在内存中的存储
数组引用:当你在代码中声明一个数组变量时,实际上是在栈上创建了一个引用(指针),指向堆上的实际数组数据。
int[] numbers;
这里,numbers
是一个栈上的引用,它还没有指向任何具体的数组对象。
数组对象:当你使用 new
关键字创建数组时,会在堆上分配一块连续的内存空间来存储数组的数据。
numbers = new int[5];
这里,new int[5]
在堆上创建了一个包含5个整数的数组,并且 numbers
引用指向了这个数组的起始位置。
数组元素:
-
对于原始类型(如
int
,char
,boolean
等),数组中的每个元素直接存储在堆中,每个元素占据固定的内存空间。 -
例如,
int
类型的每个元素占用4个字节。 -
对于引用类型(如
String
,Object
等),数组中的每个元素是一个引用,指向堆中的实际对象。
String[] names = new String[3];
names[0] = "Alice";
names[1] = "Bob";
这里,names
数组中的每个元素是一个指向 String
对象的引用。"Alice"
和 "Bob"
都是独立的对象,分别存储在堆中。
5.3 内存布局示例
假设我们有以下代码:
public class ArrayMemoryExample {
public static void main(String[] args) {
String[] names = new String[3]; // 创建一个字符串数组
names[0] = "Alice";
names[1] = "Bob";
}
}
内存布局详解:
-
栈内存 (Stack):
- 在
main
方法的栈帧中,有一个局部变量names
,它是一个引用,指向堆中的String
数组。 names
引用存储在栈内存中。
- 在
-
堆内存 (Heap):
- 当执行
String[] names = new String[3];
时,在堆内存中分配了一块空间来存储一个String
数组。这个数组有3个元素,每个元素都是一个String
引用。 - 每个
String
引用(names[0]
,names[1]
,names[2]
)也存储在堆内存中。 - 当执行
names[0] = "Alice";
和names[1] = "Bob";
时,堆内存中会创建两个String
对象"Alice"
和"Bob"
,并且names[0]
和names[1]
分别指向这两个String
对象。
- 当执行
图解: 为了更直观地理解,可以画一个简单的图示。
+-----------------+
| 栈 (Stack) |
| +-------------+ |
| | names | | <--- 指向堆中的 String[] 数组
| +-------------+ |
+-----------------+
+-----------------+
| 堆 (Heap) |
| +-------------+ |
| | String[] | | <--- 包含 3 个 String 引用
| | [0] | | <--- 指向 "Alice"
| | [1] | | <--- 指向 "Bob"
| | [2] | | <--- null
| +-------------+ |
| +-------------+ |
| | "Alice" | | <--- String 对象
| +-------------+ |
| +-------------+ |
| | "Bob" | | <--- String 对象
| +-------------+ |
+-----------------+
总结:
names
是一个引用,存储在栈内存中。names
指向的String
数组存储在堆内存中。String
数组中的每个元素(即String
引用)也存储在堆内存中。- 实际的
String
对象(如"Alice"
和"Bob"
)存储在堆内存中,并由String
数组中的引用指向。