文章目录
数组是存储 同一种数据类型多个元素的集合,也可以看成是一个容器,既可以存储 基本数据类型,也可以存储 引用数据类型;
一、 数组定义格式
格式1: 数据类型[] 数组名;
二、 数组的初始化
Java中的数组必须先初始化然后才能使用,所谓的初始化,就是为数组中的元素分配内存空间并为每个元素赋值。
- 动态初始化:只给定数组长度,由系统为数组元素赋默认的值
动态初始化格式:数据类型[] 数组名 = new 数据类型[数组长度]
int arr[] = new int[5];//5为指定的数组长度
double[] arr2=new double[2];
boolean[] arr3=new boolean[4];
char[] arr4=new char[1];
byte[] arr5=new byte[2];
//系统为数组元素赋默认值0、0.0、‘ ’、false、null
int one=arr1[1];
double two =arr2[1];
boolean three=arr3[1];
System.out.println(one); //0
System.out.println(two);//0.0
System.out.println(three);//false
- 数组的静态初始化:指定赋值,系统计算长度
静态初始化格式:数据类型[] 数组名 = {元素1,元素2,…};
double[] arr7={3.1,20,10};
三、数组的赋值
引用赋值与索引赋值
// 第九个数组指向第一个数组的地址,因此arr3==arr1
int[] arr3=arr1; //引用赋值,把第1个数组的引用arr1,赋值给第三个数组
arr3[0]=5000;// 通过索引重新赋值为5000,此时arr1[0]==5000
System.out.println(arr1[0]);//5000
System.out.println(arr3[0]);//5000
System.out.println(arr3[1]);//arr1[1]
System.out.println(arr3==arr1);//true
动态初始化引用赋值图解如下:
文字解释:
- 1、Java中的.java文件首先被JDK编译成为.class文件,系统将这个.class文件放在方法区,从程序的主入口main()方法开始执行语句;
- 2、在栈中存储数组的引用,当它读到new关键字的时候就在堆区分配了一组对应大小的连续内存空间,并且赋给数组元素默认的值,系统将开辟的空间的地址值
0x001
返回给引用arr1,因此打印arr1之后就会看到这段空间的地址; - 3、程序接下来为数组元素赋新值,它通过引用和索引找到堆区的这个元素,用新值覆盖掉旧值。
- 4、数组arr3也是一样的。程序定义了一个新的引用arr3,并将arr1的值赋给它,此时arr3就会和arr1指向同一块内存空间,
- 5、如果通过数组名arr3和下标也可以改变数组元素的值。
四、数组的内存图解
- 栈:存储的都是局部变量(在方法中或者在语句中定义的变量)和引用
- 堆:每一个使用关键字new出来的对象,系统都会给它分配一个内存地址值,并且每一个变量都会有默认的初始化值;例如数组和类实例化的对象,对象在new的时候堆会开辟内存空间拷贝类的成员变量并赋默认值
- Java中各个类型的数据的默认初始值:
byte,short,int,long------------ 0
float,double-------------------- 0.0
char----------------------------- '\u0000'
boolean-------------------------- false
引用数据类型---------------------- null
堆区的内存在使用完之后都会变成垃圾,JVM的垃圾回收器有自己的一套算法来回收这部分内存
数组的静态初始化图解:
文字解释:
-
- 方法区读取字节码文件,调用main方法;
-
- 在栈中存储数组的引用arr,堆内存开辟指定长度的空间,并赋默认值,由于我们为元素赋值,因此默认值会快速覆盖掉,然后堆返回地址值给引用值arr;
-
- 程序接下来为数组元素
arr[0]
赋新值,它通过引用和索引找到堆区的这个元素,用新值300覆盖掉旧值20。
- 程序接下来为数组元素
五、数组操作的两个常见问题
- 越界:ArrayIndexOutOfBoundsException
数组的长度:数组中元素的个数
//获取数组的长度
System.out.println(arr.length);//3
//数组一旦定义好长度就固定,arr长度为3,索引为0、1、2,当打印arr[3]时就会越界而报错。
System.out.println(arr[3]);//报错 数组角标越界异常,索引超出数组长度
- 空指针:NullPointerException
报错原因:数组已经不在指向堆内存了。而你还用数组名去访问元素
//人为置空,将存储数组地址的引用赋值为null,使得数组与引用断开联系。方便垃圾回收器及早回收
arr=null;
System.out.println(arr[0]);
六、数组的遍历、反转、最值
- 数组的遍历
int[] arr3 = {1, 2, 3, 4};
//数组的正向遍历
for (int i = 1; i < arr3.length; i++) {
System.out.println(arr3[i]);
}
System.out.println("+++++++++++++++++++++++");
//数组的反向遍历
for (int i = arr3.length - 1; i >= 0; i--) {
System.out.println(arr3[i]);
}
//快速遍历数组的方式 arr3.length.fori
- 获取数组的最值
int max = arr3[0];//定义参照人
for (int i = 0; i < arr3.length; i++) {
//方法一:
/* if(max<arr3[i]){
max=arr3[i];
}*/
//方法二:
max = max > arr3[i] ? max : arr3[i];
}
System.out.println(max);
- 数组的反转
分析:
- 循环次数是数组长度的一半,如果错写成了数组长度,他就会再次给你进行反转,相当于没有进行任何操作;
- 循环控制条件
i<=arr3.length/2
,对于偶数的长度数组,会越过中间再交换赋值一次,因此不可以加=
int[] arr = {1, 2, 3, 4, 5};
reserveArr(arr);
showArr(arr);
//定义数组反转的方法 (int[] arr)表示接收一个数组
public static void reserveArr(int[] arr) {
int midium = 0;
for (int i = 0; i < arr.length / 2; i++) {
midium = arr[arr.length - 1 - i];
//减i的作用: 为了遍历交换赋值
arr[arr.length - 1 - i] = arr[i];
arr[i] = midium;
}
//方法二:使用2个循环初始值进行遍历
//i是索引从0开始向后遍历,j是从后向前开始遍历,j一直大于i, 当i与j相汇时,值交换结束。因此循环满足条件为:i<j,当i==j就跳出循环
/*i<j为循环控制条件,表示当i==j时,跳出循环,不再交换值
for (int i=0,j=arr.length-1;i<j;i++,j--){
int t=arr[i];//存储左边的值
arr[i]=arr[j];//右边的值赋给左边
arr[j]=t;//左边的值赋给右边 */
}
//自定义方法:显示一个数组的所有元素
public static void showArr(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
七、 二维数组
-
引用数据类型:数组、类、接口,new出来的数据就是引用数据类型
-
一维数组用来表示一列数,二维数组可以表示一张表。二维数组其实就是每一个元素为一维数组的数组。
理解:学校是一个二维数组,班级为一维数组,学生是一维数组的元素 -
动态初始化定义二维数组:定义一维数组的长度,二维数组中存储一维数组地址值。
定义格式1:
数据类型[ ][ ] arr = new 数据类型[m[n];
m表示这个二维数组有多少个一维数组(必须写)
n表示每一个一维数组中有多少个元素(可以不写),建议写上,防止空指针
这些元素的数据类型都是相同的。
定义格式2:
数据类型[][] 数组名 = new 数据类型[m][];
m表示这个二维数组有多少个一维数组
这一次没有直接给出一维数组的元素个数,可以动态给出
举例:int[][] arr = new int[3][];
arr[0]=new int[2]; //动态初始化一维数组
arr[1]=new int[]{1,2,3,4}; //静态初始化一位数组
-
静态初始化定义二维数组
int[][] arr={{},{},{}}
-
需要注意的知识点:
- 因为二维数组中已经定义了存储元素类型为引用数据类型数组,系统开辟内存空间存储一维数组并从默认值null迅速存储一维数组的地址值;
new int[3][]
:系统开辟内存空间存储一个二维数组,里面有三个一位数组,初始值为null,没有定义一维数组长度,不会开辟内存空间来存储三个一位数组的地址值,
//动态初始化: 定义一维数组的长度,二维数组中存储一维数组地址值。
int[][] arr = new int[3][2];// 3 表示二维数组长度,可以存储3个一维数组;2表示二维数组中一维数组的长度
//使用数组来取出二维数组中的第一个一维数组
int[] elment = arr[0];
//为二维数组中第一个数组赋值
arr[0] = new int[]{0, 1};
//arr[0]表示二维数组中的一维数组,是引用数据类型,保存的是系统另外开辟内存空间存储的一维数组的地址值。
//打印二维数组中第一个元素的地址值。
System.out.println(arr[0]);
// arr[0][0]存储的是一维数组的元素值,是基本数据类型。保存的不是地址值。
//取出二维数组中,一维数组中存的值。
System.out.println(arr[0][0]);
//打印二维数组的地址值
System.out.println(arr);
//打印二维数组中最后一个一位数组的最后一个元素
//数组中的最后一个元素的索引=数组的长度-1
System.out.println(arr[arr.length - 1][arr[arr.length - 1].length - 1]);
//动态初始化写法二:
//后面括号不输入值:不会去初始化二维数组中的一维数组,默认值为null
int[][] arr1 = new int[3][];
System.out.println(arr1[0]);//null
System.out.println(arr1[1]);//null
System.out.println(arr1[2]);//null
//我们可以自己初始化三个一维数组,放到二维数组中
int[] one = new int[2];
arr1[0] = one;//指向one的地址值
//注意: 每次new都会开辟新的内存空间,存储的地址值不同 =表示指向地址值
arr1[0] = new int[2];//新建一个内存空间,指向新的地址值
//静态初始化:int[][] arr={{},{},{}}
int[][] arrr = {{22, 66, 44}, {77, 33, 88}, {25, 45, 65}, {11, 66, 99}};
//同时定义一个一维数组与二维数组
int[] x, y[];//x是一维数组,y是二维数组
x = new int[2];
y = new int[2][];
八 二维数组内存图详解
一、 动态初始化定义二维数组
分析:
- 程序首先进入方法区,由JVM将MyTest.java文件编译成MyTest.class文件,并找到程序的入口mian(),将方法调入虚拟机栈区执行。
- 程序读到关键字new,在堆区为数组arr分配一段连续的内存空间,并将这个地址值赋给引用arr,因为二维数组存储的是一维数组,因此分配在堆区的数据类型是一维数组(引用),因此系统默认为arr[0],arr[1],arr[2]赋值为null。
- 因为定义数组的方式给定了二维数组中一维数组的大小,因此系统很快在堆区为arr中3个一维数组分别分配两个int类型大小的空间,并返回三个地址值,分别赋给arr[0],arr[1],arr[2],所以它们三个的值被打印输出就是一个地址值。这时候数组中的每一个元素都是int类型数据,它们的默认值都是0,后续再重新赋值为20、200等
二、 静态初始化定义二维数组
分析:
- 程序首先进入方法区,由JVM将MyTest.java文件编译成MyTest.class文件,并找到程序的入口mian(),将方法调入虚拟机栈区执行。
- 程序读到关键字new,在堆区为数组arr分配一段连续的内存空间,并将这个地址值赋给引用arr,因为二维数组存储的是一维数组,因此分配在堆区的数据类型是一维数组(引用),因此系统默认为arr[0],arr[1],arr[2]赋值为null,没有指向任何内存空间。这种定义数组的方式没有给定二维数组中一维数组的长度,因此也就不存在二维数组的元素,此时如果打印数组元素,编译器会报错。
- 定义不同长度的一维数组one、two、three,并将它们的地址值赋给arr中的arr[0],arr[1],arr[2]这三个引用
arr[0]=new int[2]
表示对二维数组中的一维数组保存重新开辟int型内存空间的地址值。此时默认值为0
九、数组作为参数传递
- 一种就是基本数据类型作为参数传递,属于值传递,就是把实参的值传过去,形参的改变不影响实参。
- 一种就是引用类型作为参数传递,属于引用传递,传递的是地址值,形参的改变会影响实参。
import java.util.Random;
import java.util.concurrent.ForkJoinPool;
public class MyTest {
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println("a: " + a + ",b: " + b);
//调用方法:把 a 和 b 传过去,其实是传入10与20,方法中内部变量(10与20)的改变不影响外部变量的值。
change(a,b);
System.out.println("a: " + a + ",b: " + b);
int[] arr = {1,2,3,4,5};
//调用方法 ,把数组传递过去,传入的是地址值,形参实参指向同一个地址。
change(arr);
System.out.println(arr[1]);
}
public static void change(int a,int b) {
System.out.println("a: " + a + ",b: " + b);
a = b;
b = a + b;
System.out.println("a: " + a + ",b: " + b);
}
public static void change(int[] arr){
for(int x = 0 ; x < arr.length ; x++){
if(arr[x]%2 == 0){
arr[x] *= 2;
}
}
}
}/*输出结果为:
a: 10,b: 20
a: 10,b: 20
a: 20,b: 40
a: 10,b: 20
4*/
分析:
- 最一开始a与b的值分别为10、20,调用方法,方法里面两次输出a与b的值,变化前后不同,由于方法没有任何的返回值,因此主方法中的局部变量a与b都没有变化,调用完方法打印输出发现值不变。
- 定义一维数组并将数组名传递给经过重载的方法change(),变化数组中的元素,最终发现main()中数组元素的值改变,因为传递的参数是引用,也就相当于地址,形参变量与实参变量指向同一块内存空间,它们都可以操纵所指向的数据。
十、二维数组的遍历
练习:求二位数组中所有一维数组元素的和
//动态初始化方法
int[][] arr = new int[4][3];
//新建一维数组
int[] arr1 = {22, 66, 44};
int[] arr2 = {77, 33, 88};
int[] arr3 = {25, 45, 65};
int[] arr4 = {11, 66, 99};
//给二维数组中的一维数组赋值
arr[0] = new int[]{22, 66, 44}; //此处静态初始化不可以简写
arr[1] = arr2;
arr[2] = arr3;
arr[3] = arr4;
//静态初始化方法
// 求和
int[][] arrr = {{22, 66, 44}, {77, 33, 88}, {25, 45, 65}, {11, 66, 99}};
int count = 0;
for (int i = 0; i < arrr.length; i++) {
for (int i1 = 0; i1 < arrr[i].length; i1++) {
count += arrr[i][i1];
}
}
System.out.println(count);
练习:打印杨辉三角形
如图:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
看图分析规律:
- 从第三行第二列开始中间的每一个数字为上一行前一列和上一行本列之和
- 一个数组可以看成如下: 行数是二维数组的长度;列数是二维数组中一维数组的长度
步骤如下:解决问题要一步一步的去解决,从小问题先解决,再综合到大问题
1.打印正方形
2.打印三角形
3.令三角形首尾元素为1
4.从第三行第二列开始中间的每一个数字为上一行前一列和上一行本列之和
5.打印最终图形
Scanner scanner = new Scanner(System.in);
System.out.println("n: "); //n为二维数组包含n个一维数组
int n = scanner.nextInt();
//让行数列数相等,没有赋值的元素不输出
int[][] arrrr = new int[n][n];
// 3.令三角形首尾元素为1
for (int i = 0; i < arrrr.length; i++) {
arrrr[i][0] = 1;//每一行第一个元素为1
arrrr[i][i] = 1; //每一行最后一个元素为1。 对角线元素索引值相等
}
//从第三行第二列开始中间的每一个数字为上一行前一列和上一行本列之和
for (int i = 2; i < arrrr.length; i++) {
//j < i,最后一个元素固定为1,因此不能相等
for (int j = 1; j < i; j++) {
arrrr[i][j] = arrrr[i - 1][j - 1] + arrrr[i - 1][j];
}
}
//5.打印最终图形
for (int i = 0; i < arrrr.length; i++) {
for (int i1 = 0; i1 <=i; i1++) {
System.out.print(arrrr[i][i1]+" ");
}
System.out.println();
}
十一、递归
- 递归就是指方法自己调用自己,但是注意,只有JVM才可以调用main(),因此mian()自己不能调用自己。递归次数太多,会造成栈溢出。
- 递归体现的是拆分合并的思想
- 理解:从前有座山,山上有座庙,庙里有个小和尚在讲故事,讲的故事(调用)就是:(开始递归)从前有座山,山上有座庙,庙里有个小和尚在讲故事
练习:求5的阶乘
private static int multiple(int a) {
int result;
if (a==1) {result=1;}
else {
//累乘的思想,本身是乘方法,不断调用自身,就实现了累乘
result=a*multiple(a-1); //5*4!
};
return result;
}
图解分析:
练习:求斐波那契数列的和
public static int fibo(int a) {
if (a==1|a==2){
return 1;
}
else {
int result;
result=fibo(a-1)+fibo(a-2);
return result;
}
使用数组来求斐波那契数列的和:
int[] arr=new int[20];
arr[0]=1;
arr[1]=1;
for (int i = 2; i < arr.length; i++) {
//从第三个数开始为前两个数之和
arr[i]=arr[i-1]+arr[i-2];
}
System.out.println(arr[19]);