新年好~~~新年开篇万字博客 —Java数组的学习,有点干货,建议收藏观看!!!
本篇介绍了数组的概念,数组创建和初始化.数组的使用(元素访问,和数组遍历方法),初识引用数据类型,简单介绍JVM内存分布,认识null,堆区空间的释放
二维数组相关知识的介绍~
学习Java中的数组
- 一.数组的基本概念
- 1.为什么要使用数组?
- 2.什么是数组?
- 二.数组的创建和初始化
- 1.数组的创建格式
- 2.数组的初始化
- 三.数组的使用
- 1.数组中元素的访问
- 2.遍历数组
- ①.使用循环遍历数组
- ②.使用for each遍历数组
- ③.使用Arrays类遍历数组
- 四.初识引用数据类型
- 1.了解JVM初始内存分布
- 2.基本类型变量与引用类型变量的区别
- 3.数组属于引用类型
- ①.为什么数组属于引用类型?
- ②.使用数组实现swap方法:交换两个数的值
- 4.认识null
- 5.Java中如何释放堆区空间
- 五.二维数组
- 1.什么是二维数组?
- 2.二维数组创建和初始化
- 3.遍历二维数组
一.数组的基本概念
1.为什么要使用数组?
当我们想存放5个学生的成绩时,假设每个成绩都是百分制没有小数,那我们需要创建5个整形变量用来存放5个学生的成绩…
public class TestStudent{
public static void main(String[] args){
int score1 = 70;
int score2 = 80;
int score3 = 85;
int score4 = 60;
int score5 = 90;
System.out.println(score1);
System.out.println(score2);
System.out.println(score3);
System.out.println(score4);
System.out.println(score5);
}
}
上面代码便实现了存放五个学生成绩并且都输出了,但是如果学生人数增加到20个,甚至100个,此时一个个创建一百个变量score?你创建好了变量,输出每个学生成绩又需要写一百次…
上面书写的存放多个相同类型的变量方法虽然可行,但是太耗时了,操作起来也不方便…
有没有一种方法可以一次性创建指定个数的相同类型的变量呢?
那便是数组!
2.什么是数组?
数组顾名思义就是由多个数组成的一个集合
专业术语来讲就是:数组是有n个相同类型元素组成的一个集合,并且在内存中每个元素的物理地址是连续的…
数组可以看成是现实中的车库:
每个车位可以停放一辆车子,车位之间都是连续的…
回到数组,这六个停车位可以看做是一个整形数组,数组内有六个整形元素
每个整形元素可以存放一个整形
因为数组中每个元素地址是连续的,
规定:为区分数组内每个空间所处的位置,从第一个元素开始所处的位置为数组的0下标位置,依次往后是1下标2下标…
总结:
1 . 数组中存放的元素其类型相同
2. 数组的空间是连在一起的
3. 每个空间有自己的编号,起始位置的编号为0,即数组的下标
二.数组的创建和初始化
此次介绍的是一维数组…
1.数组的创建格式
创建一个一维数组有三种基本格式
T[] 数组名={value1,value2,value3};//格式1
T[] 数组名=new T[]{value1,value2,value3};//格式2
T[] 数组名=new T[3];//格式3
示例:
int[] arr=new int[3];//创建一个有三个整形元素的整形数组
T表示数据类型 (也表示数组中每个元素的类型)
[]表示是数组
T[] 表示是T数组类型
数组名用标识符表示, ()也可以看做是一个引用变量,存放着数组的地址用来操作数组访问数组内每个元素(具体如何操作在下面会写到)
2.数组的初始化
数组的初始化主要分为动态初始化以及静态初始化。
动态初始化
动态初始化:表示在创建数组时,直接指定数组内的元素个数,元素个数可以用整形字面常量表示,也可以用整形变量表示
int[] arr1=new int[3];//3是一个字面常量
int[] arr2=new int[n];//n为一个变量
//创建三个整形元素的整形数组
静态初始化
静态初始化:在创建数组时,没有直接指定数组内的元素个数,而是根据具体的数据内容来指定
int[] arr1={1,2,3};//格式1
int[] arr2=new int[]{1,2,3};//格式2
//格式1是格式二的简写形式
数组也可以按照如下C语言创建方式,不推荐
/*
该种定义方式不太友好,容易造成数组的类型就是int的误解
[]如果在类型之后,就表示数组类型,因此int[]结合在一块写意思更清晰
*/
int arr[] = {1, 2, 3};
静态和动态初始化也可以分为两步,但是省略格式不可以
int[] array1; //先创建一个 引用变量
array1 = new int[10]; //再给引用变量初始化
//可以先不初始化但是在使用array1前必须初始化
int[] array2;
array2 = new int[]{10, 20, 30};
// 注意下面省略格式不可以拆分, 否则编译失败
// int[] array3;
// array3 = {1, 2, 3}; error //必须前面加 new int[]
初始化时 等号右边 []里有值则后面不能加{},{}有值则[]里不能有数字
//动态初始化和静态初始化不能同时存在
int[] arr1=new int[3]{1,2,3}; //编译器会报错
【注意事项】
1.动态初始化未给数组元素确定的值,只指明了元素个数,此时每个元素会根据自己的数据类型有自己的默认值
2.动态初始化,指明元素个数的时候可以用整形字面常量,也可以用整形变量
3.静态初始化虽然没有指定数组的长度,编译器在编译时会根据{}中元素个数来确定数组的长度。(既确定了元素个数,也确定了每个元素的值)
4.静态初始化可以简写,省去后面的new T[]。(虽然省去了new T[], 但是编译器编译代码时还是会还原)这种简写法只能在创建同时初始化使用,在创建后再为其初始化时必须加上new T[]
5.动态初始化和静态初始化不能同时存在
6…初始化时, {}中数据类型必须与[]前数据类型一致。
7.等号左边的int [] 里 []只标识的是一个数组类型 里面不能有数字int[3] arr={1,2,3} error
三.数组的使用
介绍的是一维数组的基本使用 元素访问,遍历数组
1.数组中元素的访问
上面说到,数组在内存中一段连续的空间,而数组每个元素都有自己的编号(下标),第一个元素下标为0依次递增…
此时可以通过下标来访问数组的每个元素
访问方式:
数组名[下标] 表示访问的数组第(下标+1)个元素
int[]array = new int[]{10, 20, 30, 40, 50};
System.out.println(array[0]); //10
System.out.println(array[1]);//20
System.out.println(array[2]);
System.out.println(array[3]);
System.out.println(array[4]);//50
// 也可以通过[]对数组中的元素进行修改
array[0] = 100;
System.out.println(array[0]); //100
通过下标访问,不能访问范围之外的元素,否则会发生数组下标越界:
int[] array = {1, 2, 3};
System.out.println(array[3]); // 数组中只有3个元素,下标一次为:0 1 2,array[3]下标越界
// 执行结果
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
at Test.main(Test.java:4)
【注意事项】
- 数组是一段连续的内存空间,因此支持随机访问,即通过下标访问快速访问数组中任意位置的元素
- 下标从0开始,介于[0, N)之间不包含N,N为元素个数,不能越界,否则会报出下标越界异常。
2.遍历数组
所谓 “遍历” 是指将数组中的所有元素都访问一遍, 访问是指对数组中的元素进行某种操作,比如:打印,修改元素值。
下面介绍三种遍历数组(打印数组所有元素)
①.使用循环遍历数组
int[]array = new int[]{10, 20, 30, 40, 50};
System.out.println(array[0]);
System.out.println(array[1]);
System.out.println(array[2]);
System.out.println(array[3]);
System.out.println(array[4]);
上述代码可以起到对数组中元素遍历的目的,但问题是:
- 如果数组中增加了一个元素,就需要增加一条打印语句
- 如果输入中有100个元素,就需要写100个打印语句
- 如果现在要把打印修改为给数组中每个元素加1,修改起来非常麻烦。
通过观察代码可以发现,对数组中每个元素的操作方式都是相同的(通过数组下标访问),而下标是从0开始递增的那么则可以使用循环来进行遍历数组。
int[]array = new int[]{10, 20, 30, 40, 50};
for(int i = 0; i < 5; i++){
System.out.println(array[i]);
}
上面代码可以使用循环用i得到数组所有下标,打印数组所有元素,但有个缺点,数组元素不会永远是5的,但是在Java中,可以使用数组名.length获取数组的长度即数组的所有元素个数…
int[]array = new int[]{10, 20, 30, 40, 50};
for(int i = 0; i < array.length; i++){
System.out.println(array[i]);
}
②.使用for each遍历数组
for each 遍历数组 格式 为
for ( : ) 冒号左边需要定义一个代表变量 而变量的类型就是数组的元素类型 冒号右边写上数组的引用即表示遍历哪个数组
最后通过打印方法 输出每个元素的值
for (int n:array) { // 数组每个元素的类型 定义一个代表变量n : 数组的引用(数组名)System.out.println(n); //n表示数组每个元素 会从第一个元素开始往后打印}
③.使用Arrays类遍历数组
Java中提供了一个Arrays类,这个类里有很多对数组进行操作的方法
可以用来对数组排序 sort 对数组每个元素空间内填充数据fill 将数组指定长度拷贝 copyOf(可以增长缩小)…等等方法
使用前需要导入类包import Java.util.Arrays; 不用import时需要在类前面加上其类包的包路径 java.util.Array
这里介绍的是Arrays类里的toString方法:将数组所有转化为字符串的形式返回
此时可以直接打印字符串的方式打印数组所有元素
System.out.println(java.util.Arrays.toString(array)); //将array所指向的数组所有元素转换为字符串的形式 打印出来
四.初识引用数据类型
Java中分基本数据类型 和引用数据类型 在这篇博客中介绍到了基本数据类型->基本数据类型
1.了解JVM初始内存分布
内存可以看作是一段连续的存放数据的空间排列组成的,主要用来存放程序运行时的数据
- 程序运行时代码需要加载到内存
- 程序运行产生的中间数据要存放在内存
- 程序中的常量也要保存
- 有些数据可能需要长时间存储,而有些数据当方法运行结束后就要被销毁
既然有这么多数据都需要存放在内存,而如果对内存中存储的数据不加区分的随意存储,那对内存管理起来将会非常麻烦。
因此JVM也对所使用的内存按照功能的不同进行了划分:
程序计数器 (PC Register):
只是一个很小的空间, 保存下一条执行的指令的地址
虚拟机栈(JVM Stack):
与方法调用相关的一些信息,每个方法在执行时,都会先创建一个栈帧,栈帧中包含 有:局部变量表、操作数栈、动态链接、返回地址以及其他的一些信息,保存的都是与方法执行时相关的一 些信息。
比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。
本地方法栈(Native Method Stack):
本地方法栈与虚拟机栈的作用类似. 只不过保存的内容是Native方法的局 部变量. 在有些版本的 JVM 实现中(例如HotSpot), 本地方法栈和虚拟机栈是一起的.
堆(Heap):
JVM所管理的最大内存区域. 使用 new 创建的对象都是在堆上保存 (例如前面的 new int[]{1, 2, 3}
),堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销 毁。
方法区(Method Area):
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. 方法编译出的的字节码就是保存在这个区域
2.基本类型变量与引用类型变量的区别
基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的是其所对应的值;
而引用数据类型创建的变量,一般称为对象的引用.引用变量,其空间中存储的是对象所在空间的地址。
基本数据类型和引用数据类型创建的局部基本变量和引用变量都是在内存的虚拟机栈申请开辟的一块空间.
但不同的是,基本变量是可以直接存放对应的值: 例如 int a=10;
而引用变量存放的是对象的地址…
简单了解一下对象:
对象是需要使用new关键词创建出来的,并且对象也就是根据对应的类实例化出来的一个实体…
对象的创建是在堆区中申请开辟空间的,因此引用变量是在虚拟机栈申请空间,而内部存放的是堆区中申请的对象空间地址(有点类似C语言中的指针,但是Java中引用要比指针的操作更简单)
当基本变量和引用变量都作为方法的实参传递时的区别:
二者传递的都是变量里面的值的一份拷贝,
基本变量里的值给方法对应的形参接受(传值调用),此时形参保存的字面常量只能进行一些运算,而覆盖形参的值最后方法结束后并不会影响实参基本变量里的值!!!
引用变量里存的值实际是地址传递给方法对应的形参接受(传址调用),此时形参保存的地址可以直接访问到所指向的对象,对对象进行操作,而此时覆盖形参内的地址,会失去对象的地址,但是在被修改前.如果通过地址访问修改了对象内的数据,在方法结束后,对象的数据也会被修改了
3.数组属于引用类型
①.为什么数组属于引用类型?
1.在上面提到数组的创建初始化格式都用到了new这个关键字,int[] arr=new int[3];
而上面也说到new是用来创建对象的关键字,数组则看可以看成是实例化的数组对象,实例化后返回了在堆区中的地址. int[] arr则是创建了一个引用变量(整形数组类型的变量),用来接收返回的数组地址,arr数组名就是对象的引用!
2.在遍历数组时我们用到了数组名.length获取到了数组的元素个数,如果数组名是一个基本变量是无法做到的,同时还能用数组名调用很多方法进行操作,这更说明了 数组名就是一个引用类型变量.接收的是实例化后的数组对象地址!
public static void main(String[] args) {int[] arr = new int[3];System.out.println(arr);}
而通过直接输出arr 显示的是一串字符,[ 表示是一个一维数组 @是分隔 而后面的是Hashcode 哈希值也可以暂时理解为是数组对象的地址
②.使用数组实现swap方法:交换两个数的值
既然数组是引用类型,则写一个swap方法以数组名作为实参传参调用,实现交换两个数的值
示例:
public static void main(String[] args) { //swap方法实现两数交换int[] arr={1,2,3};System.out.println("交换前:"+arr[0]+" "+arr[1]);swap(arr);System.out.println("交换后:"+arr[0]+" "+arr[1]);}public static void swap(int[] arr){int tmp=arr[0];arr[0]=arr[1];arr[1]=tmp;}
可以看到成功使用数组 实现swap方法完成两数的交换,可见数组是属于引用类型的,而引用变量保存的是对象地址
总结: 所谓的 “引用” 本质上只是存了一个地址. Java 将数组设定成引用类型, 这样的话后续进行数组参数传参, 其实
只是将数组的地址传入到函数形参中. 这样可以避免对整个数组的拷贝(数组可能比较长, 那么拷贝开销就会很大)
4.认识null
null 在 Java 中表示 “空引用” , 也就是一个不指向对象的引用.
当我们创建了引用变量,但暂时还不确定给其什么地址,则可以赋值一个null,表示不指向任何对象如
int[] arr=null;
但是如果引用变量里是null,则表示什么对象都没指向,此时不能通过引用变量访问堆区的对象空间否则会报空指针异常
示例:
int[] arr = null;
System.out.println(arr[0]);
// 执行结果
Exception in thread "main" java.lang.NullPointerException
at Test.main(Test.java:6)
arr引用变量此时什么都没指向,而又通过arr[0]访问0下标的元素则会出问题
null 的作用类似于 C 语言中的 NULL (空指针), 都是表示一个无效的内存位置. 因此不能对这个内存进行任何读写操作. 一旦尝试读写, 就会抛出 NullPointerException.
注意: Java 中并没有约定 null 和 0 号地址的内存有任何关联
5.Java中如何释放堆区空间
在栈区的空间,一般是存放局部变量的,而局部变量出了局部范围,生命周期结束所申请开辟的空间也就被释放还给操作系统了
调用方法所申请的栈帧也是如此,方法执行栈帧开辟,方法结束,栈帧销毁
而堆区里的空间是给new实例化出的对象使用的,而空间申请后会返回指向该空间的地址,此时通常都会创建一个与对象相同类型的引用变量来接收其地址
类似于在C语言中动态内存开辟,而C语言需要手动使用free函数释放,而释放完后还要及时将指向被释放的空间置空,这种手动操作,有些时候很容易忘记,容易造成内存泄漏
而在Java里,JVM里有内存回收机制会自动帮我们回收掉不需要使用的堆区空间,即当堆区申请开辟的空间没有任何引用变量里存放了指向该空间的地址时,这块空间会被自动回收掉,再也不需要手动释放,这也是Java受很多程序员喜爱的一点
示例:
public static void main(String[] args) {int[] arr = new int[3];System.out.println(Arrays.toString(arr));arr=null; //将引用变量置空,此时 数组对象没有引用指向会被自动释放}
在遍历完数组后将arr置空表示没有指向任何对象 ,此时这个数组对象没有被任何引用指向且当堆区内存空间不够时此块内存区域会被JVM自动回收~
五.二维数组
1.什么是二维数组?
上面讲的是数组是一维数组能存放一行多列元素,而二维就是行和列,二维数组能够存放多行多列元素
在Java中的二维数组,实际上也可以看作是一个一维数组,但是这个一维引用数组每个元素都是一个引用变量存放着指向一维数组对象的地址…
以创建一个二维整形数组为例:
int[][] arr1={{1,2,3},{4,5,6},{4,5,6}};
System.out.println(arr1);
//输出二维数组创建的引用变量
System.out.println(arr1[0]);
//arr1指向的引用数组的第一个元素
System.out.println(arr1[0][0]);
//arr1指向的引用数组第一个元素指向的整形数组的第一个元素
引用变量其地址输出了[ [ 可以看作是指向二维数组的地址实际上指向的是一个引用数组地址
而引用数组第一个元素存放的是指向一个整形数组的地址
整形数组第一个元素即输出了1
2.二维数组创建和初始化
二维数组初始化也分静态初始化和动态初始化
数据类型[][] 数组名称 = new 数据类型 [行数][列数] { 初始化数据 };
int[][] arr1={{1,2,3},{4,5,6}}; // 静态初始化 简写 两行三列
int[][] arr2=new int[][]{{1,2,3},{4,5,6}}; //静态初始化 详写 两行三列 {}里的子括号个数代表行数 子括号里的数据代表列个数
int[][] arr3=new int[2][3]; //动态初始化 :直接指定行数列数 元素类型是整形 所以默认值为0
Java中二维数组创建时行不能省略.列可以省略:
int[][] arr4=new int[2][]; //确定了两行,列暂时没有确定
System.out.println(arr4[0]); //输出null 动态初始化 :没有确定多少列 此时一维引用数组的两个元素 默认为null空引用
int[][] arr5=new int[][3]; //error 创建的一维引用数组不确定行数 会编译报错
int[][] arr6=new int[][]{{},{}}; //有两个子括号表示两行 ,但是行内又没有元素 此时创建的一维引用数组两个元素指向没有元素的一维数组(不是空引用) 没有意义
Java中二维数组可以行列不规则:
上面说到,Java静态初始化时子括号个数代表行数,而子括号里的元素个数代表的是列数,而不同的子括号里的元素个数可以不同
动态初始化时,不可以省略行.可以省略列,此时数组名指向的一维引用数组的每一个元素暂时默认值是为null.此时可以为每一个元素都new一个任意元素个数的一维数组存放其地址
示例:
int[][] arr1=new int[][]{{1,2,3},{4,5},{6}};int[][] arr2=new int[3][];arr2[0]=new int[]{1,2,3};arr2[1]=new int[]{4,5};arr2[2]=new int[]{6};//一个引用类型数组有三个引用变量分别存放具有不同元素个数的整形数组System.out.println(arr1[2][0]);System.out.println(arr2[2][0]);
注意:
1.二维数组静态初始化和动态初始化不能同时存在
2.二维数组动态初始化时行不可以省略,列能省略
3.省略列时,引用数组里的每个元素默认为null
4.静态初始化子括号的个数为行数(对应的引用类型数组的元素个数),子括号内的数据个数为列数(对应的基本数据类型数组的元素个数)如果子括号内没有数据,则指向的基本数据类型数组没有元素(没有意义)
5.二维数组的行列可以不规则(每一行的列数可以不同)
3.遍历二维数组
上面介绍的是一维数组的遍历,而二维数组有行有列遍历方式相比一维要复杂一点
通过双层循环方式遍历
public static void main(String[] args) {int[][] arr={{1,2,3},{4,5,6},{7,8,9}}; //双层循环遍历二维数组for (int i = 0; i < arr.length; i++) {for (int j = 0; j < arr[i].length; j++) {System.out.println(arr[i][j]);}}}
通过for each方式遍历
public static void main(String[] args) { int[][] arr={{1,2,3},{4,5,6},{7,8,9}}; //for each方式遍历二维数组for (int[] x:arr) { //arr指向一维引用数组 int[] x作为引用数组每个元素代表for (int y:x) { //x作为引用数组每个元素代表 是指向每个整形数组的引用 int y作为每个整形数组的元素代表System.out.println(y); //输出每个整形数组的每个元素 }}
通过Arrays类方法遍历
public static void main(String[] args) {int[][] arr={{1,2,3},{4,5,6},{7,8,9}};//通过Arrays类方法遍历System.out.println(Arrays.deepToString(arr));//深度遍历将二维数组所有元素转换为字符串形式返回
一维数组二维数组比较常用,基本操作方式全部介绍完,当然还有三维,四维数组但是不常见
再次助新年快乐!!!