目录
1.什么是数组?
数组就是用来存储一批同种类型数据的内存区域(可以理解成容器)。
示例代码如下:
int[] arr = {10, 20, 30, 40, 50};
String[] names = {"张三", "李四", "王五"};
2.数组的初始化
数组初始化定义包括静态初始化数组和动态初始化数组,二者在使用中有一定区别。
2.1静态初始化数组
定义数组的时候直接给数组赋值,即数组定义出来后,里面的元素值是确定的。
格式:
// 静态初始化数组完整格式
数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3,...};
示例代码如下:
double[] scores = new double[]{68.9,77.3,90.5,85.0};
int[] ages = new int[]{18,20,35,19};
// 静态初始化数组简化形式
数据类型[] 数组名 = {元素1,元素2,元素3,...}
示例代码如下:
int ages[] = {18,20,35,19};
数组的基本原理:
等号左侧为在内存中创建的一块区域,用存放数组变量,等号右侧为new出来的一个连续对象,其中存放各个元素(静态初始化数组元素值为给定值,动态初始化数组元素值为默认值)。在创建的时候,将此对象地址交由等号左侧的数组变量存储,这样在访问该数组变量时就能获取存放在该数组变量中的对象的地址。
示例代码如下:
String[] names = {"张三", "李四", "王五"};
System.out.println(names); // [Ljava.lang.String;@1540e19d 为new String[]{"张三", "李四", "王五"}的地址
注意:数组变量中存储的是数组在内存中的地址,数组是引用类型(数组变量中存放的是地址,不是具体内容,根据此地址去寻找具体内容)。
引用类型存放的是地址,基本数据类型存放的是数据。
2.1.1数组的访问
索引:数组中各个元素的编号(从0开始)。
在进行数组访问时,数组变量中存放的是指向数组对象的地址,再根据索引定位在这个地址中具体是哪个元素
数组的访问: 数组名[索引]
示例代码如下:
int[] arr = {12, 24, 36};
// 取值:数组名称[索引]
System.out.println(arr[0]); // 12
// 赋值:数组名称[索引] = 数据;
arr[2] = 100;
System.out.println(arr[2]); // 100
length:记录数组长度(元素个数),arr.length是数组的属性,不带”()”。
示例代码如下:
// 获取数组长度,即该数组中的元素个数
int[] arr = {12, 24, 36};
System.out.println(arr.length); // 3
数组的最大索引可以怎么表示?
数组名.length - 1 //前提:元素个数大于0
2.1.2数组注意事项
(1)“数据类型[] 数组名”也可以写成”数据类型 数组名[]”([]和数组名可以颠倒)。
示例代码如下:
// 1.“数据类型[] 数组名”也可以写成”数据类型 数组名[]”([]和数组名可以颠倒)。
int[] ages = {11, 20, 28};
// int ages[] = {11, 23, 28};
(2)什么类型的数组必须存放什么类型的数据,否则报错。
示例代码如下:
// 2.什么类型的数组必须存放什么类型的数据,否则报错。
int[] number = {30, 40, "张三"}; // 报错,需要int型数据
(3)数组一旦被定义,在程序的执行过程中,其长度、类型就固定了。
示例代码如下:
// 3.数组一旦被定义,在程序的执行过程中,其长度、类型就固定了。
String[] names = {"张三", "李四", "王五"};
System.out.println(names[3]); // 报错,数组names[]长度固定为3,访问第4个元素,超过数组边界异常ArrayIndexOutOfBoundsException: 3
2.2动态初始化数组
定义数组的时候只确定元素的类型和数组的长度,之后再存入具体数据。
数组的动态初始化格式:
数据类型[] 数组名 = new 数据类型[长度];
示例代码如下:
double[] scores = new double[3]; // 0.0, 0.0, 0.0
// 0 1 2
// 赋值
scores[0] = 99.5;
System.out.println(scores[0]); // 99.5
System.out.println(scores[2]); // 0.0
动态初始化数组的元素默认值
元素默认规则
数据类型 | 明细 | 默认值 |
基本类型 | byte、short、char、int、long | 0 |
float、double | 0.0 | |
boolean | false | |
引用类型 | 类、接口、数组、String | null |
示例代码如下:
// 1.整型数组的元素默认值都是0
int[] intArr = new int[3];
System.out.println(intArr[0]); // 0
System.out.println(intArr[2]); // 0
// 2.字符型数组的元素默认值是0
char[] charArr = new char[4];
System.out.println(charArr[0]); // 打印不到,因为0在字符型中没有对应值
System.out.println((int) charArr[2]); // 进行强制类型转换,判断字符型数组的元素默认值是0
// 3.浮点型数组的元素默认值是0.0
double[] doubleArr = new double[4];
System.out.println(doubleArr[0]); // 0.0
System.out.println(doubleArr[3]); // 0.0
// 4.布尔型数组的元素默认值是false
boolean[] booleanArr = new boolean[5];
System.out.println(booleanArr[0]); // false
System.out.println(booleanArr[2]); // false
// 5.引用数据类型数组的元素默认值是null
String[] strArr = new String[2];
System.out.println(strArr[0]); // null
System.out.println(strArr[1]); // null
注:字符型数组元素默认值为0,因为0在字符型中没有对应值,所以打印不到,需要进行强制类型转换为int型(没有运算不会自动转换)。
静态初始化数组与动态初始化数组使用场景有什么区别?
静态初始化:开始就存入元素值,适合当前已经知道存入的元素值的业务场景。
动态初始化:只指定数组长度,后期再赋值,适合当前已经知道数据的数量,但是还不清楚要存入哪些具体元素值的业务场景。
两种初始化数组方式的格式是独立的,不可以混用!!!
3.数组的遍历
遍历:就是一个一个数据的访问。
为什么要遍历?搜索、数据统计等等都需要用到遍历。
如何遍历数组?
通过一个for循环,索引从0开始,判断条件为小于该数组长度,就可以循环遍历出数组中的每一个元素。
示例代码如下:
int[] arr = {10, 22, 36, 75};
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
4.数组的案例
4.1数组元素求和
需求:某部门5名员工的销售额分别是:16、26、36、6、100,请计算出他们部门的总销售额。
分析:
①拿到这些数据 -->使用数组。
②遍历数组中的每个数据,然后在外面定义求和变量,把这些数据累加起来。
示例代码如下:
public static void main(String[] args) {
// 1.定义一个静态初始化数组,存储这些数据
int[] arr = {16, 26, 36, 6, 100};
// 2.定义求和变量,用于存放累加数值
int sum = 0;
// 3.遍历数组中的每个数据,然后把这些数据累加起来
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
// 4.将求和变量输出
System.out.println("总销售额为" + sum); // 184
}
4.2数组求最值
需求:某部门6名员工的销售额分别是:15、9000、10000、20000、9500、-5,请计算出他们的最大销售额。
分析:
①拿到这些数据 -->使用数组。
②定义一个变量用于记录最大值,这个变量建议默认存储第一个元素值(避免出现自定义的初始变量值是最值的情况),此时第一个元素作为参照,从第二个元素开始遍历。
示例代码如下:
public static void main(String[] args) {
// 1.定义一个静态初始化数组,存储这些数据
int[] arr = {15, 9000, 10000, 20000, 9500, -5};
// 2.定义变量,用于存储最大值,建议使用第一个值作为参照
int maxNum = arr[0];
// 3.变量数组中的每一个元素,依次与当前最大值变量存储的数据进行比较,若该元素较大,则该元素替换最大值变量存储的数据
for (int i = 1; i < arr.length; i++) { // 第一个元素作为参照,从第二个元素开始遍历
if (arr[i] > maxNum) {
maxNum = arr[i];
}
}
// 4.将最大值变量输出
System.out.println("最大销售额为" + maxNum); // 20000
}
4.3猜数字游戏
需求:开发一个幸运小游戏,游戏规则如下:
游戏后台随机生成1-20之间的5个数(无所谓是否重复),然后让大家来猜数字:
➢ 未猜中提示:“未命中”,并继续猜测
➢ 猜中提示:“运气不错,猜中了”,并输出该数据第一次出现的位置,且输出全部5个数据,最终结束本游戏。
分析:
①随机生成5个1-20之间的数据存储起来 --> 使用数组
②定义一个死循环,输入数据猜测,遍历数组,判断数据是否在数组中,如果在,进行对应提示并结束死循环;如果没有猜中,提示继续猜测直到猜中为止。
示例代码如下:
public static void main(String[] args) {
// 需求:定义5个1-20之间的随机数,让用户猜测,若要提示猜中
// 还要输出该数据在数组中第一次出现是第几位,并打印数组内容,若没有猜中则继续
// 1.定义一个动态初始化数组存储5个随机的1-20之间的数据
int[] arr = new int[5];
// 2.动态生成5个1-20之间的随机数并存储到数组中去
Random r = new Random();
for (int i = 0; i < arr.length; i++) {
arr[i] = r.nextInt(20) + 1;
}
// 3.使用一个死循环让用户猜测
Scanner sc = new Scanner(System.in);
OUT:
// OUT标签,用于标记循环,可用break OUT;或continue OUT;跳出整个被OUT标记的循环
while (true) {
System.out.println("请输入1-20之间的整数数字:");
int num = sc.nextInt();
// 4.遍历数组中的每个数据,看是否有数据与猜测的数据相同,相同则代表猜中,且需要给出相应提示
for (int i = 0; i < arr.length; i++) {
if (num == arr[i]) {
System.out.println("运气不错,猜中了,该数字第一次出现在第" + (i + 1) + "位");
// 5.输出数组的全部数据
System.out.print("5个数字为:");
for (int j = 0; j < arr.length; j++) {
System.out.print(arr[j] + "\t");
}
// break; // 只会结束内部遍历各个元素的for循环
break OUT; // 结束整个死循环
}
}
System.out.println("未命中");
}
}
break用于跳出包含它的最内层循环。
OUT标签:用于标记循环,可用break OUT;或continue OUT;跳出整个被OUT标记的循环。
4.4随机排名
需求:某公司开发部5名开发人员,要进行项目进展汇报演讲,现在采取随机排名后进行汇报。
请先依次录入5名员工的工号,然后展示出一组随机的排名顺序。
分析:
①随机生成5名员工的工号数据存储起来 --> 使用数组
②依次遍历数组中的每个元素,随机一个索引数据,让当前元素与该索引位置处的元素进行交换。
示例代码如下:
public static void main(String[] args) {
// 键盘录入一组工号,最终打乱顺序随机一组出来作为排名顺序
// 1.动态初始化一个数组,存储5个工号
int[] codes = new int[5];
// 2.定义一个循环,循环5次,每次录入一个工号,存储到相应位置
Scanner sc = new Scanner(System.in);
for (int i = 0; i < codes.length; i++) {
// 正式录入工号
System.out.println("请输入第" + (i + 1) + "个员工工号:");
int code = sc.nextInt();
// 将输入的工号存入到数组中
codes[i] = code;
}
// 3.遍历数组中的每个元素,然后随机一个索引出来,让该元素与随机索引位置的元素进行值交换(本节重点)
Random r = new Random();
// 定义一个临时变量存储随机索引位置的元素值
int temp = 0;
// 打印排序前的数据
System.out.println("原顺序为:");
for (int i = 0; i < codes.length; i++) {
System.out.print(codes[i] + "\t");
}
System.out.println();
for (int i = 0; i < codes.length; i++) {
// 当前遍历的元素值:codes[i]
// 随机一个索引位置出来
int num = r.nextInt(codes.length);
temp = codes[num];
codes[num] = codes[i];
codes[i] = temp;
}
// 4.遍历数组元素后打印,就是随机排名后的结果
System.out.println("随机后的顺序为:");
for (int i = 0; i < codes.length; i++) {
System.out.print(codes[i] + "\t");
}
}
4.5数组排序——冒泡排序
数组排序就是对数组中的元素,进行升序(由小到大)或者降序(由大到小)的操作。
冒泡排序的思想:从头开始,两两比较,若前大后小则交换数据。以此类推,每次从数组中找出最大值,放到数组的最后面。
如:第一轮:用数组第0个索引值”5”与第1个索引值”2”进行比较,若第0个索引值”5”大于第1个索引值”2”,则两个具体值交换位置,即第0个索引值为”2”与第1个索引值”5”,否则不变。然后再用数组第1个索引值”5”与第2个索引值”3”进行比较,依次类推。就可以找出第一轮比较中所有元素的最大值,且将其放到了数组最后一位。
然后进行第二轮比较,找到剩下数据中的最大值(即所有数据中的次大值),放到数组中的倒数第二位。
以此类推,直至所有数据都被排序完毕。
关键步骤分析:
第1轮 比较次数 3
第2轮 比较次数 2
第3轮 比较次数 1
①确定总共需要做几轮:数组长度 - 1
②每轮比较几次:数组长度 - 当前循环轮数
示例代码如下:
public static void main(String[] args) {
// 1.动态初始化一个数组,存储4条数据
int[] arr = new int[4];
// 2.定义一个循环,循环4次,每次录入一个工号,存储到相应位置
Scanner sc = new Scanner(System.in);
for (int i = 0; i < arr.length; i++) {
// 正式录入数据
System.out.println("请输入第" + (i + 1) + "个数据");
int num = sc.nextInt();
// 将数据存储到数组中
arr[i] = num;
}
// 打印排序前的数据
System.out.println("原数据为:");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
// 定义一个临时变量存储第j个索引(即占位元素索引)的元素值
int temp = 0;
// 3.定义一个循环,控制比较轮数
for (int i = 0; i < arr.length - 1; i++) {
// i ==0 比较次数 3 j == 0 1 2
// i ==1 比较次数 2 j == 0 1
// i ==2 比较次数 1 j == 0
// 比较次数 = (arr.length - (i + 1))
// 4. 定义一个循环控制每轮比较的次数,占位
for (int j = 0; j < arr.length - (i + 1); j++) {
// 判断j当前位置元素值是否大于后一个位置元素值,若是,则交换
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
System.out.println();
// 5.遍历数组元素后打印,就是从小到大冒泡排名后的结果
System.out.println("排序后的的数据为:");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
}
5.数组的内存图
5.1Java内存分配、数组内存图
Java为了执行Java程序,以便于管理和维护,将Java内存区域进行了一定的划分,分为:栈、堆、方法区、本地方法栈、寄存器。
方法区是字节码文件加载时进入的内存区域。栈内存是方法运行时所进入的内存,变量也是存入在此。new出来的东西(对象)会在堆内存中开辟空间并产生地址。
Java在内存中具体如何执行,原理是什么?
示例代码如下:
public static void main(String[] args) {
// 基本数据类型中存储的是数据本身
int a = 10;
System.out.println(a); // 10
int[] arr = {11, 22, 33};
// 引用数据类型中存放的是等号右侧对象的地址,根据地址访问数据
System.out.println(arr); // [I@1540e19d
arr[0] = 44;
arr[1] = 55;
arr[2] = 66;
System.out.println(arr[0]); // 44
System.out.println(arr[1]); // 55
System.out.println(arr[2]);// 66
}
如下方数组内存图所示,Java程序在执行过程中,首先把.java类文件编译为.class文件,加载到方法区中。
第二步,将main方法提取到栈内存中运行,开始执行main方法中的代码。在栈内存中开辟一块区域,代表a变量,并将值10赋给该变量,因此直接打印a变量时,输出结果为值10。开辟一块区域,代表arr数组变量,然后在堆内存中开辟一块区域,代表new的数组{11, 22, 33},其中分为三小块区域,每块小区域内存放各个索引的值,然后将该数组的地址赋给栈内存中的arr数组(引用数据类型)进行存储,在arr中存储的地址指向堆内存中new出来的数组的起始位置。
第三步,根据arr数组中存储的new出来的数组对象的地址,找到该数组对象,再通过索引值访问各个元素的具体值,并分别赋值44,55,66。
第四步,同样根据arr数组中存储的new出来的数组的地址,找到该数组对象,再通过索引值访问各个元素的具体值,并将他们依次打印出来。
5.2两个变量指向同一个数组
在开发中,有时会将某个数组赋值给另一个数组,这样就会产生两个变量指向同一个数组的情况。
两个变量指向同一个数组的执行原理是什么?
示例代码如下:
public static void main(String[] args) {
int[] arr1 = {11, 22, 33};
// 把数组arr1变量赋值给int类型的数组变量arr2
int[] arr2 = arr1; // 赋给arr2的是数组{11, 22, 33}对象在堆内存中的地址
System.out.println(arr1); // [I@1540e19d
System.out.println(arr2); // [I@1540e19d
// 将arr2数组中第1个索引位置值赋为99
arr2[1] = 99;
System.out.println(arr1[1]); // 99
System.out.println(arr2[1]); // 99
}
如下方数组内存图所示,Java程序在执行过程中,首先把.java类文件编译为.class文件,加载到方法区中。
第二步,将main方法提取到栈内存中运行,开始执行main方法中的代码。开辟一块区域,代表arr1数组变量,然后在堆内存中开辟一块区域,代表new的数组{11, 22, 33},其中分为三小块区域,每块小区域内存放各个索引的值,然后将该数组的地址赋给栈内存中的arr1数组(引用数据类型)进行存储,在arr1中存储的地址指向堆内存中new出来的数组的起始位置。
第三步,开辟一块区域,代表arr2数组变量,将数组变量arr1中的数据(new出来的数组对象的地址)赋给数组变量arr2,此时两个变量的地址是一样的,数组变量arr2中存储的地址同样指向堆内存中new出来的数组的起始位置。因此打印数组arr1和arr2中存储的值,均为[I@1540e19d。
第四步,根据arr2数组中存储的new出来的数组对象的地址,找到该数组对象,再通过索引值访问第1个索引位置的具体值,并分别赋值99。
第五步,根据数组arr1和arr2中存储的new出来的数组的地址,找到该数组对象,再通过索引值访问第1个索引位置的具体值(此时已经被更改为99),并将他打印出来,打印结果均为99。
6.数组使用的常见问题
问题1:如果访问的元素位置超过最大索引,执行时会出现ArrayIndexOutOfBoundsException(数组索引越界异常)
示例代码如下:
public static void main(String[] args) {
int[] arr = {11, 12, 13};
System.out.println(arr[0]); // 11
System.out.println(arr[1]); // 12
System.out.println(arr[2]); // 13
// System.out.println(arr[3]); //报错,数组索引越界异常,超出数组的最大索引2
// System.out.println("程序结束"); // 第6行代码报错,此行代码不会执行
}
此时程序报错Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3,且不会执行下面的代码。
问题2:如果数组变量中没有存储数组的地址,而是null, 在访问数组信息时会出现NullPointerException(空指针异常)
示例代码如下:
int[] arr = {11, 12, 13};
// 将数组arr的值赋为null,即在此数组变量中存储的堆内存中的数组对象的地址为null
arr = null;
System.out.println(arr); // null
// System.out.println(arr.length); // 报错,空指针异常,找不到数组的地址,因此也获取不到数组长度
// System.out.println(arr[0]); // 报错,空指针异常,找不到数组的地址,因此也获取不到数组中某个索引的值
}
此时程序报错Exception in thread "main" java.lang.NullPointerException
将数组arr的值赋为null,即在此数组变量中存储的堆内存中的数组对象的地址为null,将该数组变量中存储的值(堆内存中new出来的数组对象的地址)打印出来,为null。找不到数组的地址,因此也获取不到数组长度和数组中某个索引的值,程序报错。
7.Debug工具的使用
Debug是程序员用来观察代码、查看代码流程以及定位程序bug位置的一个调试工具。
IDEA自带断点调试(排错)工具,可以控制代码从断点开始一行一行的执行,然后详细观看程序执行的情况。
Debug工具基本使用步骤:
①在需要控制的代码行左侧,点击一下,生成断点;
②选择使用Debug方式启动程序,启动后程序会在断点暂停;
③控制并调试代码一行一行的往下执行。
如下图红色矩形框所示,此功能为“恢复程序”,继续执行程序,直至到下一个断点或程序结束。