数组
1、数组的使用
数组的概述
数组就是装着相同数据类型变量的容器。数组中的变量称之为元素。
要使用数组,需要数组的初始化才可以使用
数组是大小固定不变,内存连续的。
数组的声明
int[] arr1;//推荐使用
int arr2[];
数组的初始化
//动态初始化
int[] arr1 = new int[5];
//静态初始化
int[] arr2 = new int[]{1,2,3,4,5};
int[] arr3 = {1,2,3,4,5};
数组元素的访问
int[] arr1 = {1,2,3,4,5};
Sysem.out.println("arr1[0] = " + arr1[0]);
//更改数组索引为0的值为6
arr1[0] = 6;
Sysem.out.println("arr1[0] = " + arr1[0]);
2、数组中的内存分配
数组内存分配的核心原则
- 数组变量(引用)存储在栈内存中,仅保存数组对象的内存地址。
- 数组对象(实际数据)存储在堆内存中,是连续的内存块。
- 堆内存中的数组对象会被 Java 垃圾回收器自动回收(当没有引用指向它时)。
一维数组的内存分配
以 int[] arr = new int[3]; 为例,内存分配过程如下:
- 声明数组变量:
int[] arr在栈内存中创建一个引用变量arr,此时未指向任何对象(值为null)。 - 创建数组对象:
new int[3]在堆内存中分配一块能存储 3 个int类型元素的连续空间,并自动初始化默认值(int类型默认值为 0)。 - 关联引用与对象:栈内存中的
arr变量保存堆内存中数组对象的地址,形成引用关系。
栈内存 堆内存
+--------+ +---------+
| arr |--------->| [0, 0, 0]| // 长度为3的int数组,默认值为0
+--------+ +---------+
3、数组中常见的两个问题
ArrayIndexOutOfBoundsException数组索引越界NullPointerException空指针异常
int[] arr = {1,2,3,4,5};
// ArrayIndexOutOfBoundsException 数组索引越界
System.out.println(arr[5]);
//`NullPointerException` 空指针异常
arr = null;
System.out.println(arr[0]);
扩展
// 使用equals方法时,尽量使非null字符串 .equals();减少空指针异常出现;
String str = null;
if("y".equals(str)){
System.out.println("ok");
}
4、数组的遍历
for循环遍历
int[] arr = {1,2,3,4,5};
for(int i = 0;i < arr.length;i++){
System.out.println(arr[i]);
}
使用JDK5特性增强for循环,缺点:不能通过操作数组元素
int[] arr = {1,2,3,4,5};
for(int i : arr){
System.out.println(i);
}
使用Arrays.toString()直接打印
int[] arr = {1,2,3,4,5};
System.out.println(Arrays.toString(arr));
5、数组的最值
通过循环来遍历数组,并依次比较各元素的大小
int[] arr = {3,2,5,7,1,0,6};
int max = arr[0];
for(int i = 1; i < arr.length;i++){
if(max < arr[i]){
max = arr[i];
}
}
System.out.println("arr数组的最大值为" + max);
6、数组的反转
数组的反转可以通过重新定义一个数组来存储反转后的数组实现
int[] arr1 = {1,2,3,4,5};
int[] arr2 = new int[arr1.length];
for(int i = 0; i < arr1.length; i ++) {
arr2[arr1.length-1-i] = arr[i];
}
arr1 = arr2;
System.out.println("反转后的数组为" + Arrays.toString(arr1));
上述方法需要定义一个新的数组,不太推荐,思考是否有其他方案
发现可以直接交换数组元素,设置两个变量来表示索引
int[] arr = {1,2,3,4,5};
for(int min = 0,max = arr.length; min < max; min++,max--) {
int temp = arr[min];
arr[min] = arr[max];
arr[max] = temp;
//思考此处交换数据是否还有其它写法
}
System.out.println(Arrays.toString);
再次对上述代码进行优化得到
int[] arr = {1,2,3,4,5};
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;
}
System.out.println(Arrays.toString);
7、数组的查找
普通的查找,通过遍历数组依次比较
int[] arr = {1,2,3,4,5,6};
// 需要找到的值
int number = 3;
for(int i = 0;i < arr.length; i++) {
if(arr[i] == number) {
System.out.println("该值的索引为" + i);
break;
}
}
二分查找
对有序数组进行查找
int[] arr = {1,2,3,4,5,6,7,8,9};
int number = 8;
int min = 0;
int max = arr.length - 1;
int mid = (min + max)/2;
boolean ok = false;
while(min < max){
if(arr[mid] < number){
min = mid;
mid = (min + max)/2;
}else if(arr[mid] > number){
max = mid;
mid = (min + max)/2;
}else if(arr[mid] == number){
ok = true;
System.out.println("该值的索引为" + mid);
break;
}
}
if(ok){
System.out.println("该值没找到");
}
8、数组的排序
排序的方法有很多种,这里只介绍一下冒泡排序,选择排序
//可以直接使用Arrays.sort()进行排序,该排序会直接改变数组;
int[] arr = {1,4,5,7,32,6,69,23};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
冒泡排序
int[] arr = {1,4,5,7,32,6,69,23};
for(int i = 1;i < arr.length;i++){
for(int j = 0;j < arr.length - i;j++){
if(arr[j] < arr[j + 1]){
//交换数据
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j+1] = temp;
}
}
}
System.out.println(Arrays.toString(arr));
选择排序
int[] arr = {1,4,5,7,32,6,69,23};
for(int i = 0; i < arr.length-1; i++) {
for(int j = i + 1; j < arr.length;j++) {
if(arr[i] > arr[j]){
//交换数据
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
System.out.println(Arrays.toString(arr));
9、数组的扩容
数组的扩容本质上是创建一个新的数组,并且把老数组的数据全部拷贝过去
方法一:创建一个新数组,把老数组的数据全部拷贝过去;
int[] arr1 = {1,2,3,4,5};
System.out.println(Arrays.toString(arr1));
int[] arr2 = new int[arr1.length * 2];
for(int i = 0; i < arr1.length; i++){
arr2[i] = arr1[i];
}
arr1 = arr2;
System.out.println(Arrays.toString(arr1));
方法二:使用Arrays.copyOf
int[] arr = {1,2,3,4,5};
System.out.println(Arrays.toString(arr));
//Arrays.copyOf需要两个参数
//参数1:需要扩容的数组
//参数2:扩容后的长度
Arrays.copyOf(arr,arr.length*2);
System.out.println(Arrays.toString(arr));
方法三:使用System.arraycopy
int[] arr1 = {1,2,3,4,5};
System.out.println(Arrays.toString(arr1));
int[] arr2 = new int[arr1.length * 2];
// 参数一: 要拷贝的数组
// 参数二: 要拷贝数组的数据的起始索引
// 参数三: 要拷贝到的数组
// 参数四: 要拷贝到的数组的数据的起始索引
// 参数五: 要拷贝的数据长度
System.arraycopy(arr1,0,arr2,0,5);
System.out.println(Arrays.toString(arr1));
10、二维数组[了解]
Java 二维数组的特点
- 本质是数组的数组:二维数组中的每个元素都是一个一维数组,因此各行的长度可以不同(称为 “不规则数组” 或 “锯齿数组”)。
- 声明与初始化分离:可以先声明数组,再初始化;也可以在声明时直接初始化。
- 内存存储:二维数组本身和内部的一维数组在内存中是分开存储的,并非必须是连续的内存块。
- 访问方式:通过两个索引(行索引和列索引)访问元素,索引从 0 开始。
声明与初始化方式
1. 声明时直接初始化
// 方式1:完整初始化(规则数组,行列固定)
int[][] matrix1 = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// 方式2:不规则数组(各行长度不同)
int[][] matrix2 = {
{1, 2},
{3},
{4, 5, 6}
};
2.声明二维数组(不指定大小)
// 声明二维数组(不指定大小)
int[][] matrix3;
// 初始化:先指定行数,再分别初始化每行
matrix3 = new int[3][]; // 3行,列数暂不确定
matrix3[0] = new int[2]; // 第0行有2列
matrix3[1] = new int[3]; // 第1行有3列
matrix3[2] = new int[1]; // 第2行有1列
// 给元素赋值
matrix3[0][0] = 10;
matrix3[1][2] = 20;
3.声明时指定行列数(规则数组)
// 声明一个3行4列的规则数组(所有行长度相同)
int[][] matrix4 = new int[3][4];
// 赋值(默认初始值为0)
matrix4[0][0] = 1;
matrix4[2][3] = 5;
访问与遍历二维数组
1. 访问元素
通过 数组名[行索引][列索引] 访问具体元素:
int value = matrix1[1][2]; // 获取第1行第2列的元素(值为6)
2.遍历数组
常用 for 循环或增强 for 循环遍历:
// 方式1:普通for循环(适合需要索引的场景)
for (int i = 0; i < matrix1.length; i++) { // matrix1.length 是行数
for (int j = 0; j < matrix1[i].length; j++) { // matrix1[i].length 是第i行的列数
System.out.print(matrix1[i][j] + " ");
}
System.out.println(); // 换行
}
// 方式2:增强for循环(适合仅遍历元素的场景)
for (int[] row : matrix1) { // 遍历每行(row是一维数组)
for (int num : row) { // 遍历行中的每个元素
System.out.print(num + " ");
}
System.out.println();
}
注意事项
- 数组长度:
二维数组名.length表示行数;二维数组名[行索引].length表示该行的列数。 - 默认值:未手动赋值的元素会有默认值(数值型为 0,布尔型为
false,引用类型为null)。 - 空指针风险:如果某行未初始化(如
matrix3[0] = null),访问该行元素会抛出NullPointerException。