22_一维数组
一维数组的基本概念
- 当需要在Java程序中记录单个数据内容时,则声明一个变量即可。
- 当需要在Java程序中记录多个类型相同的数据内容时,则声明一个一维数组即可,一维数组本质上就是在内存空间中申请一段连续的存储单元(一个个连续的小格子),地址连续。变量的本质是在内存空间中申请一个内存单元。
- 数组是相同数据类型的多个元素的容器,元素按线性顺序排列,在Java语言中体现为一种引用数据类型。
一维数组的声明方式
-
数据类型[] 数组名称(变量名) = new 数据类型[数组长度];---------------->数据类型决定的是我们申请的内存空间/存储单元有多大并且是什么数据类型的,new是Java中的关键字代表创建、新建,能放多个数据由数组长度决定。
-
调用数组的length属性可以获取数组的长度:数组名.length
-
可以通过下标(元素的编号)的方式访问数组中的每一个元素。需要注意的是:数组的下标从0开始,对于长度为n的数组,下标的范围是0 ~ n-1。
/* 编程实现一维数组的声明和使用 */ public class ArrayTest { public static void main(String[] args) { // 1、声明一个长度为2元素类型为int类型的一维数组 // 数据类型[] 数组名称 = new 数据类型[数组长度]; 数组名、类名、变量名都是标识符的一种,严格遵从标识符的命名空间 int[] arr = new int[2]; // 前后两个数组类型必须相等 推荐该方式,更容易与变量的声明区分,提高了代码的可读性 // int arr1[] = new int[2]; // 两种方式从结果上来说是一样的,不推荐使用 C和C++中依旧使用,所以保留了它 // int num = 2; // 声明一个初始值为2的变量 // 2、打印一维数组的长度以及每个元素的数值 System.out.println("数组的长度是:" + arr.length); // 长度为2,下表为0-1 System.out.println("下表为0的元素是:" + arr[0]); // 0 System.out.println("下表为1的元素是:" + arr[1]); // 0 默认值是0 // 编译Ok,运行时发生ArrayIndexOutOfBoundsException数组下标越界异常 // System.out.println("下表为2的元素是:" + arr[2]); // 数组下标越界异常 System.out.println("---------------------------------------------"); // 使用for循环打印数组中的所有元素 for(int i = 0; i < arr.length; i ++) { System.out.println("下表为" + i + "的元素是:" + arr[i]); // 代码的重复性变低 } } }
一维数组的初始化方式
-
基本类型的数组(数组元素为基本类型)创建后,其元素的初始值:byte、short、char、int、long为0;float、double为0.0;boolean为false。
-
结论:当我们声明了一个数组,只指定了长度,但没指定初始值时,这些数组中的元素都有默认值。
-
动态方式:给定长度,但没给定初始值
-
静态方式:可以在数组声明的同时进行初始化,具体如下:
-
数据类型[] 数组名称 = {初始值1, 初始值2, …};
// 声明一个长度为5 元素类型为double类型的一维数组 double[] arr2 = new double[5]; // 动态方式 // 打印数组中每个元素值 for(int i = 0; i < arr2.length; i ++) { System.out.println("下表为" + i + "的元素是:" + arr2[i]); // 全是 0.0 } // 结论:当我们声明了一个数组,只指定了长度,但没指定初始值时,这些数组中的元素都有默认值 System.out.println("---------------------------------------------"); // 5、声明数组的同时就对数组中的元素进行初始化 静态方式 静态方式的简化写法 char[] arr3 = {'a', 'b', 'c', 'd'}; // 打印数组中的每个元素值 for(int i = 0; i < arr3.length; i ++) { System.out.println("下表为" + i + "的元素是:" + arr3[i]); // a b c d } System.out.println("---------------------------------------------"); // 6、特殊写法---------> 依然是 静态方式 静态方式的完整写法 boolean[] arr4 = new boolean[]{true, true, false, false}; for(int i = 0; i < arr4.length; i ++) { System.out.println("下表为" + i + "的元素是:" + arr4[i]); // true true false false }
-
-
数组的初始化就两种:要么给长度,它取默认初始值(动态初始化);要么给初始值,它可以算出长度(静态初始化)。
内存结构分析
- 概念:当声明一个变量时本质上就是在内存空间中申请一块存储单元。声明一维数组的时候 int[] arr 和变量的声明 int num 相比,只是多了一个[]。按照之前的原理,int num 会在内存空间中申请一块存储单元/存储区域,int[] arr 也会在内存空间中申请一块存储单元/存储区域。但是数组声明的时候又有一个 new(新建、创建)。而数组的声明,int[] arr1 是在申请存储单元,new int[2] 也是在申请存储单元,还使用 = 把 int[] arr1 进行初始化,那么,二者之间到底是什么关系呢?
内存结构之栈区
- 栈用于存放程序运行过程当中所有的局部变量。从内存空间中划分的一块区域,一个运行的Java程序从开始到结束会有多次变量的声明。一个Java程序中所有局部变量的声明,本质上都会在栈中申请内存空间。在方法体中直接声明的变量就是局部变量。
- int num,int[] arr1 在执行时本质上它们的底层都会在栈中申请一个内存空间
内存结构之堆区
- JVM会在其内存空间中开辟一个称为"堆"的存储空间,这部分空间用于存储使用new关键字创建的数组和对象。所有通过new出来的都在堆区。
整个白色的面板我们就把它当作是内存条的区域,栈区就好比在内存区域中申请了一块地盘,两条竖线代表的是我们在整个内存区域中划分出来的一块区域,这片区域我们就叫栈区。区域还很大,我们又在另外一块地盘又申请了一块区域,叫做堆区。只是人为命名了两块区域。内存0x10是new int[2] 的内存地址,= 把这个地址信息放到栈中arr1,这样我们 arr1 就可以通过内存地址拿到堆中对应地址的数据了。这里就是通过 = 把堆和栈相关联了。
int num = 2;
/**
num是一个局部变量,局部变量都在栈区中申请内存空间。
运行这行代码的时候, 就是在栈区中申请了一块int类型大小的区域,里面放的数据是2。
理解:在栈区中申请了一块地盘,这块地盘给了一个名字叫num的变量,里面的值放的是2。-----------如下图
*/
int[] arr1 = new int[2];
/**
对于数组的声明,先执行左边,再执行右边,然后执行 = 。这个 = 叫做赋值运算符。当我们执行左边的时候,它实际上是相当于,声明了一个局部变量,而只要声明了一个局部变量,就意味着要在栈区中申请一块地盘给arr1。-----如下图
然后后面new int[2] 我们说了所有new 的东西都在堆区,申请了两个int。相当于在堆区中申请了两个int那么大的内存空间。这块内存空间的长度是2,那么下标应该是0和1。我们声明数组的时候由于没给初始值,它会有默认值,此处的默认值是两个0。
接下来,等号左边的内存图和等号右边的内存图也画完了,接下来就剩一个等号了。= 叫赋值,赋值运算符的作用是:等号右边的值赋给等号左边的变量名。而右边又是申请了一块内存区域。内存区域肯定会有一个地址,在堆区中申请了一块地盘,那么,这块地盘肯定得有一个标识符。这个标识符叫内存地址,假设是0x10,代表的是这块内存空间的地址。
= 号是将右边的值赋给左边的变量名,右边只有一块内存区域,唯一能代表这块内存区域的就是内存地址,于是将这块区域的地址拷贝了一份放到了栈区中。实际上,arr1这个变量在栈区中放的是一个堆区的地址信息,通过堆区的这个地址信息,可以找到堆区中的那块内存空间。有了堆区的这块内存空间,可以通过变量名可以取得里面的数据,而里面的数据代表的是堆区中的内存地址,通过内存地址可以找到这块内存区域,然后再通过下标可以把里面的数据取出来并且进行打印或者使用。这样一来,栈区和堆区就进行的关联了。这才是声明数组的本质。等号左边做的事情和等号右边做的事情实际上是不一样的。
一旦建立了关联,以后我们再想找地址为 0x10 的这块堆区的时候,我们只需要找栈区中的arr1这个变量名即可,通过arr1可以一路找过来,因为它们已经建立了关系。
*/
基本数据类型和引用数据类型最根本的区别:基本数据类型的变量内存空间中直接放的是具体的数据内容,而引用数据类型的变量内存空间中存放的是一个内存地址,是一个指向堆区的地址。也就是基本数据类型的变量,给一个小格子就行了,而引用数据类型的变量底层很复杂,比基本数据类型赋值多了。数组是一个数据类型,数组名仅仅记录的是一个代表地址的东西。
// 7、直接通过数组名来打印数组中的所有元素
System.out.println("arr = " + arr); // arr = [I@411f53a0 这就是地址信息
// 所以我们通过数组名无法打印数组信息,要想打印数组中的所有元素,只能通过数组名加下标的形式打印
// 数组名可以找到内存空间,再通过下标可以取到里面的数据内容,然后再打印出来,这才是变量和一维数组的底层内存结构
案例:-------------此处的图片只画了堆区,栈区没有画
-
声明一个长度为5元素类型为int类型的一维数组,打印数组中所有元素值;
-
使用11、22、33、44分别对数组中前4个元素赋值后再次打印;
-
将元素55插入到下标为0的位置,原有元素向后移动,再打印所有元素值;
-
将元素55从数组中删除,删除方式为后续元素向前移动,最后位置置为0并打印;
-
查找数组中是否存在元素22,若存在则修改为220后再次打印所有元素;
/* 编程实现一维数组的增删改查操作 */ public class ArrayOpTest { public static void main(String[] args) { // 1、声明一个长度为5元素类型为int类型的一维数组,打印数组中所有元素值; int[] arr = new int[5]; // 打印数组中所有元素值 System.out.print("数组中的元素有:"); for(int i = 0; i < arr.length; i ++) { System.out.print("arr[" + i +"] = " + arr[i] + "\t"); } System.out.println(); System.out.println("------------------------------------------------------------------------"); // 2、使用11、22、33、44依次对数组中前4个元素赋值后再次打印; /* arr[0] = 11; arr[1] = 22; arr[2] = 33; arr[3] = 44;*/ for(int i = 0; i < arr.length - 1; i ++) { arr[i] = (i + 1) * 11; } // 打印数组中所有元素值 System.out.print("数组中的元素有:"); for(int i = 0; i < arr.length; i ++) { System.out.print("arr[" + i +"] = " + arr[i] + "\t"); // 11 22 33 44 0 } System.out.println(); System.out.println("------------------------------------------------------------------------"); // 3、将元素55插入到下标为0的位置,原有元素向后移动,再打印所有元素值; /*arr[4] = arr[3]; arr[3] = arr[2]; arr[2] = arr[1]; arr[1] = arr[0]; arr[0] = 55;*/ for(int i = arr.length - 1; i > 0; i --) { arr[i] = arr[i-1]; } arr[0] = 55; // 打印数组中所有元素值 System.out.print("数组中的元素有:"); for(int i = 0; i < arr.length; i ++) { System.out.print("arr[" + i +"] = " + arr[i] + "\t"); // 55 11 22 33 44 } System.out.println(); System.out.println("------------------------------------------------------------------------"); // 4、将元素55从数组中删除,删除方式为后续元素向前移动,最后位置置为0并打印; /*arr[0] = arr[1]; arr[1] = arr[2]; arr[2] = arr[3]; arr[3] = arr[4]; arr[4] = 0;*/ for(int i = 0; i < arr.length - 1; i ++) { arr[i] = arr[i+1]; } arr[arr.length-1] = 0; // 打印数组中所有元素值 System.out.print("数组中的元素有:"); for(int i = 0; i < arr.length; i ++) { System.out.print("arr[" + i +"] = " + arr[i] + "\t"); // 11 22 33 44 0 } System.out.println(); System.out.println("------------------------------------------------------------------------"); // 5、查找数组中是否存在元素22,若存在则修改为220后再次打印所有元素; for(int i = 0; i < arr.length; i ++) { if(22 == arr[i]) { arr[i] = 220; break; } } // 打印数组中所有元素值 System.out.print("数组中的元素有:"); for(int i = 0; i < arr.length; i ++) { System.out.print("arr[" + i +"] = " + arr[i] + "\t"); // 11 220 33 44 0 } System.out.println(); } }
一维数组的优缺点
优点:
- 可以直接通过下标(或索引)的方式访问指定位置的元素,速度很快。---------因为数组是一段连续的空间,都有下标,可以通过下标快速定位
缺点:
- 数组要求所有元素的类型相同
- 数组要求内存空间连续,并且长度一旦确定就不能修改
- 增加和删除元素时可能移动大量元素,效率低。
案例:一维数组之间元素拷贝实现
-
声明一个初始值为 11 22 33 44 55 的一维数组并打印所有元素
-
声明一个长度为3元素类型为int类型的一维数组并打印所有元素
-
实现将第一个数组中间3个元素赋值到第二个数组中
-
再次打印第二个数组中的所有元素
/* 编程实现数组之间元素的拷贝 */ public class ArrayCopyTest { public static void main(String[] args) { // 1、声明一个初始值为 11 22 33 44 55 的一维数组并打印所有元素 // int[] arr = new int[]{11, 22, 33, 44, 55}; 静态声明 int[] arr = {11, 22, 33, 44, 55}; // 打印数组中的所有元素 System.out.print("第一个数组中的元素有:"); for(int i = 0; i < arr.length; i ++) { System.out.print("arr[" + i + "] = " + arr[i] + "\t"); // 11 22 33 44 55 } System.out.println(); System.out.println("---------------------------------------------------------"); // 2、声明一个长度为3元素类型为int类型的一维数组并打印所有元素 int[] brr = new int[3]; // 打印数组中的所有元素 System.out.print("第二个数组中的元素有:"); for(int i = 0; i < brr.length; i ++) { System.out.print("brr[" + i + "] = " + brr[i] + "\t"); // 0 0 0 } System.out.println(); System.out.println("---------------------------------------------------------"); // 3、实现将第一个数组中间3个元素赋值到第二个数组中 brr[0] = arr[1]; brr[1] = arr[2]; brr[2] = arr[3]; // 4、再次打印第二个数组中的所有元素 System.out.print("第二个数组中的元素有:"); for(int i = 0; i < brr.length; i ++) { System.out.print("brr[" + i + "] = " + brr[i] + "\t"); // 0 0 0 } System.out.println(); } }
一维数组之间元素的拷贝优化
-
jdk API手册----------------->Overview------------------>java.base模块-------------------->java.lang包--------------------->System类------------------>arraycopy方法----------->arraycopy(Object src, int srcPos, Object dest, int destPos, int length)------------------------>第一个参数:数据来源数组,第二个参数:数据来源起始位置,第三个参数:数据去向数组,第四个参数:数据所到目标地址的起始下标,第五个参数:元素的长度/个数
-
原理:图只画了堆区的
System.out.println("---------------------------------------------------------"); // 3、实现将第一个数组中间3个元素赋值到第二个数组中 /*brr[0] = arr[1]; brr[1] = arr[2]; brr[2] = arr[3];*/ /* for(int i = 0; i < brr.length; i ++) { brr[i] = arr[i + 1]; }*/ // 可以直接使用Java官方提供的拷贝功能 // 表示将数组arr中下标从1开始的3个元素拷贝到数组brr中下标从0开始的位置 System.arraycopy(arr, 1, brr, 0, 3);
一维数组之间拷贝的笔试考点
// 5、笔试考点
// 表示将变量 arr 的数值赋值给变量 brr, 覆盖变量 brr 中原来的数值
// 数组名arr的内存空间中存放的是数据在堆区中的内存地址信息,复制后让 brr 变量中也存放了arr所指向堆区的内存地址
// 也就是让 brr 和 arr指向了同一块堆区空间,本质上就是改变指向而已
// 严格来说 brr 中的0x30被0x20覆盖了,此处只是为了不破坏数据的原始性,让我们看起来更直观
brr = arr;
System.out.print("第二个数组中的元素有:");
for(int i = 0; i < brr.length; i ++) {
System.out.print("brr[" + i + "] = " + brr[i] + "\t"); // 0 0 0
}
System.out.println();
// 改变了指向之后,原有的0x30内存空间就没有人指向了,没有人指向了的话,0x30内存空间就丢了。此时Java虚拟机有另外一个机制:垃圾回收机制,当它扫描到这块内存空间没有任何人使用的时候,它就将可以这块内存空间释放掉
案例:一维数组统计数字次数
-
编程统计用户输入一个正整数中每个数字出现次数的统计并打印
-
比如:123123 => 1 出现2次,2 出现2次,3 出现2次
-
原理分析:任意一个正整数拆分出来的数字:0 ~ 9,拆分数字:不断用数字对10取余再对10取商。10进制中的任意数字只可能是0~9之间的。统计方法:拆分出来的数字就是数组的下标,数组出现的次数就是数组下标对应的元素值。
-
编码实现:
/* 编程使用数组实现正整数中每个数字出现次数的统计 */ import java.util.Scanner; public class ArrayCountTest { public static void main(String[] args) { // 1、提示用户输入一个正整数并使用变量记录 // 当在程序中要记录多条数据的时候使用数组 System.out.println("请输入一个正整数:");; Scanner sc = new Scanner(System.in); int num = sc.nextInt(); // 2、准备一个长度为10元素类型为int类型的一维数组, 默认值为 0 int[] arr = new int[10]; // 3、拆分正整数中的每个数字并统计到一个一维数组中 int temp = num; // 保证原始数据完整性 while(temp > 0) { // 明确循环条件但不明确循环次数 arr[temp%10] ++; temp /= 10; } // 4、打印最终的统计结果 for(int i = 0; i < arr.length; i ++) { if(arr[i] > 0) { // 出现了才打印,不出现就不打印 System.out.println("数字" + i + "出现了" + arr[i] + "次!"); } } } }
案例:数组实现学生考试成绩的录入和打印
-
提示用户输入学生的人数(变量)以及每个学生的考试成绩(数组)并打印出来。-----------------一个数组的长度可以是一个变量
-
计算该班级的总分和平均分,并打印出来。
/* 编程使用数组来记录学生的考试成绩 声明数组的长度还可以使用变量 */ import java.util.Scanner; public class ArrayScoreTest { public static void main(String[] args) { // 1、提示用户输入学生的人数并使用变量记录 System.out.println("请输入学生的人数:"); Scanner sc = new Scanner(System.in); int num = sc.nextInt(); // 2、根据学生人数来声明对应长度的数组负责记录学生的考试成绩 // 变长数组:主要指变量可以作为数组的长度,但绝不是数组的长度可以发生改变 int[] scores = new int[num]; // 3、提示用户输入每个学生的考试成绩并记录到一维数组中 int scoreSum = 0; for(int i = 0; i < num; i ++) { System.out.println("请输入第" + (i + 1) + "个学生的考试成绩:"); scores[i] = sc.nextInt(); scoreSum += scores[i]; } // 4、打印所有学生的考试成绩 System.out.println("本班学生的考试成绩分别是:"); for(int i = 0; i < scores.length; i ++) { System.out.print(scores[i] + " "); } System.out.println("本班学生考试成绩的总分是:" + scoreSum + ", 平均分是:" + (scoreSum / num)); } }
案例:数组实现学生成绩总分和平均分的计算
// 5、计算本班学生的总分以及平均分并使用变量记录
int sum = 0;
for(int i = 0; i < scores.length; i ++) {
sum += scores[i];
}
double avg = sum/num*1.0;
// 6、打印最终的计算结果
System.out.println("本班级学生的总分是:" + sum + ", 平均分是:" + avg);
是:");
for(int i = 0; i < scores.length; i ++) {
System.out.print(scores[i] + " ");
}
System.out.println("本班学生考试成绩的总分是:" + scoreSum + ", 平均分是:" + (scoreSum / num));
}
}