就是这篇博客,CSDN富文本编辑器里面ctrl+z把我5000字全给弄没了,我真的哭晕在键盘上,最后还是含着眼泪写完了这篇博客,尽量做到比之前更加详细,只要大家的支持和关注,就是误删了八百万字我也重写/(ㄒoㄒ)/~~
好,那我们进入正题
【本节目标】
- 🤯理解数组的基本概念
- 🤯掌握数组的基本用法
- 🤯数组与方法互操作
- 🤯熟练掌握数组相关的常见问题和代码
数组的基本概念
为什么要使用数组?
回答:简化代码,集合管理。
举个简单里例子,我们现在要存入5个学生的JavaSE考试成绩,并对其进行输出
如果这样写的话,太麻烦了:
public class TestStudent{
public static void main(String[] args){
int score1 = 70;
int score2 = 70;
int score3 = 70;
int score4 = 70;
int score5 = 70;
System.out.println(score1);
System.out.println(score2);
System.out.println(score3);
System.out.println(score4);
System.out.println(score5);
}
}
这段代码没有问题,但是过多的话就不好管理,
它们都有一个共同点,数据类型都是相同的,那么我们就可以利用数组来集约化管理
什么是数组
数组:可以看成时相同类型元素的一个集合。在内存中时一段连续的空间。
- 🎏数组中存放的元素类型相同
- 🎏数组中空间连接在一起
- 🎏每个空间有着自己的编号,其首位的位置的编号为0,即数组的下标。
那么在程序中我们应该如何创建数组呢?
数组的创建以及初始化
数组的创建
T[] 数组名 = new T[N];
T:表示数组中存放元素的类型
T[]:表示数组的类型
N:表示数组的长度
int[] array1 = new int[10];//创建一个可以容纳10个int类型元素的数组
double[] array2 = new double[5];//创建一个可以容纳5个double类型元素的数组
String[] array3 = new String[3];//创建一个可以容纳三个字符串元素的数组
public static void main1(String[] args) {
int[] array = {1,2,3,4,5,6,7,8,9,10};
int ret = array[2];
System.out.println(ret);
// 下标默认从0开始
int[] array2 = new int[]{1,2,3,4,5,6,7,8,9,10};
// 两种是一样的,一般用第一种
int[] array3 = new int[10];
// 不确定里面放什么的话,就用第三种
}
}
数组的初始化
数组的初始化主要分为动态初始化以及静态初始化
1、🍕动态初始化:在创建数组时,直接指定数组中元素的个数
int[] array = new int[10];
2、🧇静态初始化:在创建数组时不直接指定数组元素个数,而直接讲具体的数据内容惊醒指定
语法格式:T[] 数组名称 = {data1,data2,data3......}
public static void main3(String[] args) {
int[] array ;
// 我们称这样为动态初始化
array = new int[]{1,2,3,4,5};
array = new int[]{9,8,7,6,5};
// 这样重新定义是可以的
int[] array2 = {1,2,3,4,5};
// 我们称这么做为静态初始化
//
// array2 = {9,8,7,6,5}这样写会报错,因为定义数组的时候整体赋值,只有一次机会
}
public static void main2(String[] args) {
double[] array = new double[10];
String[] strings = new String[10];
// 此时我们并没有初始化,但静态初始化虽然没有指定数组的长度,编译器会自动帮你确定
}
注意事项:
👉🍔静态初始化虽然没有指定数组的长度,编译器在编译时会根据{}中的元素个数来确定 数组的长 度
👉🍳静态初始化时,{}中数据类型必须与[]前数据类型一致。
👉🌭静态初始化可以简写,省去后面的new T[]
👉🍿数组也可以按照如下C语言个数创建,但是最好不要这么做咯👇
/*
该种定义方式不太友好,容易造成数据的类型就是int的误解
[]如果在类型之后,就表示数组类型,因此int[]结合在一起写,意思更加明了
**/*
int arr[] = {1,2,3};
👉🍟如果不确定数组当中内容时,使用动态初始化,否则建议使用静态初始化
👉🍱静态和动态初始化也可以分为两步,但是省略格式不可以,上面代码中有提到
例如这样就会编译失败→int[] arr3; arr3 = {1,2,3}
👉🍘如果没有对数组进行初始化,数组中元素有其默认值
1、如果数组中存储元素类型为基本类型,默认值时基类类型对应的默认值,如:
类型默认值byte0short0int0long0float0.0fdouble0.0char/u0000booleanfalse
2、如果数组中存储元素类型为引用类型,默认值为null.
数组的使用
数组中元素访问
数组在内存中是一段连续的空间,空间的编号都是从0开始的,依次递增,该编号成为数组的下标,数组可以通过下标访问其任意位置的元素。比如:
public static void main3_3(String[] args) {
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]);
// 也可以通过[]对数组中的元素进行修改
array[0] = 100;
System.out.println(array[0]);
}
【注意事项】
- 🍡数组是一段连续的内存空间,因此支持随机访问,即通过下标访问快速访问数组中任意位置的元素
- 🍭下标从0开始,介于[0,N)之间不包含N,N为元素个数,不能越界,否则会报出下标越界异常。
这里的红字异常就是提醒我们使用数组一定要下标谨防越界
遍历数组
所谓的“遍历”是指讲数组中的所有元素都访问一遍,访问是指对数组中的元素进行某种操作,比如:Print.
上述代码可以起到对数组中元素遍历的一个目的,但任然有一些问题:
- 🛹如果数组中增加了一个元素,就需要增加一条打印语句
- 🚃如果输入里面有100个元素,就需要去写100个打印语句
- 🌎如果现在要把打印修改为给数组中每个元素加一个数,会非常麻烦。
那么细心的小伙伴们就会想到了,我们可以运用循环来进行打印。
下面介绍一些循环遍历的方法👇
1、基本for循环
PS:在数组中,我们可以通过 ***数组对象.length ***来获取数组的长度
2、for-each进阶法——for增强版💪
for-each是for循环的另外一种使用方法,能够更方便得完成对数组得遍历。可以避免循环条件和更新语句写错。 (循环每次都会把array数组中的元素逐个赋给x)
3、超级简便toString法
我们会常常这样打印
System.out.println(Arrays.toString(array));
嗨嗨!每天一个小细节,学到了!
数组是引用类型
初始JVM的内存分布
内存是一段连续的储存空间,主要用来存储程序运行时数据的。比如:
- 💰程序运行时代码需要加载到内存
- 📸程序运行时产生的中间数据要存放在内存
- 🎥程序中的常量也要保存
- 🎸有些数据可能需要长时间的存储,而有些数据当方法运行结束后就要被销毁
如果对内存中存储的数据不加区分的随意存储,那对内存管理起来会非常麻烦
就像一个房间从这样
经过管理之后变成这样
因此为了管理内存,JVM也对所使用的内存按照功能的不同进行了划分
- 🚗程序计数器(PC Register):只是一个很小的空间,用来保存下一条执行的指令的地址
- 🚓虚拟机栈(JVM Stack):与方法调用相关的一些信息,每个方法在执行的时候,都会先创建一个栈帧(啊啊啊啊啊啊啊又是让我破大防的栈帧!),栈帧中包含有:局部变量表、操作数栈、动态链接、返回地址以及其他一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。当方法运行结束之后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。
- 🚕本地方法栈(Native Method Stack):本地方法栈与虚拟机栈的作用类似,只不过保存的内容时Native方法的局部变量。在有些版本的JVM实现中(例如HotSpot),本地方法栈和虚拟机栈是一起的。
- 🛺堆(Heap):JVM所管理的最大内存区域。使用new创建的对象都是在堆上保存的(例如new int[] {1,2,3}),堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在石油,就不会被销毁。
- 🚙方法区(Method Area):用于存储已经被虚拟机加载的类信息、常量、静态变量、即使编译器编译过后的代码等数据。方法编译出来的字节码就是保存在这个区域内的
我们在C语言中一般都需要用malloc();来开辟动态内存,然后再去用free()释放
但是在Java中,堆上开辟的内存不需要我们进行释放,JVM本身具有的垃圾回收机制可以帮我们进行这些操作
这一块的内容就不多做解释了,等以后我了解得更透彻更详细会再进行细说
基本类型变量与引用类型变量的区别
基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的时其所对应的值;
而引用数据类型创建的变量,我们一般称之为对象的引用,其空间中存储的是对象所在空间的地址值。
上面的代码中,a,b,arr,array1都是函数内部的变量,因此其空间都在main方法对应的栈帧中分配。
a、b是内置类型的变量,因此其空间中保存的就是给该变量初始化的值。
array是数组类型的引用变量,其内部保存的内容可以简单理解成是数组在堆空间中的首地址
我们从上面的图中可以见到,引用变量并不直接储存对象本身,可以简单理解为储存的是对象在堆中空间的起始地址。通过该地址,引用变量便可以再去操作对象。这么做有点类似于C语言中的指针,但是Java中引用要比指针的操作简便更多。
引用变量(进阶)
话不多说,咱们直接上代码👇
// 对象 引用
public static void main7(String[] args) {
int[] array1 = {1,2,3,4};
int[] array2 = array1;
System.out.println("array1:" + Arrays.toString(array1));
System.out.println("array2:" + Arrays.toString(array2));
System.out.println("======================");
array2[0] = 99;
// 这样用array2改变首元素的值时,我们测试可以发现array1[0]也变了
System.out.println("array1:" + Arrays.toString(array1));
System.out.println("array2:" + Arrays.toString(array2));
int[] array3={1,2,3,4};
int[] array4={5,6,7,8};
array3=array4;
// 这个时候{1,2,3,4}这块内存就被JVM的垃圾回收机制回收了
System.out.println(Arrays.toString(array3));
System.out.println(Arrays.toString(array4));
}
public static void main6(String[] args) {
// 该方法用于了解引用变量
int[] array = {1,2,3};
double[] array2 ={1.0,2.0,3.0};
System.out.println(array);
System.out.println(array2);
int[] array3 = null;//代表array这个引用变量不指向任何对象
// 不能用int array3 = 0;来初始化引用变量
// 而且这个时候 System.out.println(array3);的话
// 会显示空指针异常
// System.out.println(array3[0]);也会显示空指针异常
// 既然引用变量为null了,那么就不能用array.length以及其他类似的用法了,会报错
}
上述图片中的初始化方法中省略了new,但编译器还是当作new了一块内存处理。
认识null
null在Java中表示“空引用”,也就是一个不指向对象的引用
null的作用类似于C语言中的NULL(空指针),都是表示一个无效的内存位置。因此不能对这个内存进行任何读写操作,一旦尝试读写,就会抛出NullPointerException.
注意:Java中并没有约定null和0号地址的内存有任何关联
一些引用变量的问题:
数组的应用
保存数据
public static void main (String[] args) {
int[] array = {1,2,3};
for(int i =0;i < array.length;++i){
System.out.println(array[i] + " ");
}
}
作为函数的参数
1、参数传递基本数据类型
public static void func2(int[] array){//在栈上新分配一块array引用变量的空间,也指向main_func2中的{1,2,3,4}
array = new int[]{11,12,13,14};//这里在堆上新new了一块数组对象{11,12,13,14},array指向该对象
}
//这里有个重要的概念,在Java中,都是按值传递,地址也是值
public static void main_func2(String[] args) {
int[] array1={1,2,3,4};
func2(array1);//这里把array1的地址值传过去了,
System.out.println(Arrays.toString(array1));
}
// 输出结果为1 2 3 4
这里在方法中修改形参的值,其实并不影响实参的num值
2、参数传递数组类型(引用数据类型)
我们发现在func方法内部修改数组的内容,方法外部的数组内容也发生改变,
因为数组式引用类型,按照引用类型来进行传递,是可以修改其中存放的内容的。
总结:所谓的“引用”本质上只是存了一个地址。Java将数组设定成引用类型,这样的话后续进行数组参数传参,其实只是将数组的地址传入到方法形参中,这样可以避免堆整个数组的拷贝,对内存的无谓损耗。
测试实验的代码如下👇
//实验一
public static void func1(int a) {
a=20;
}
public static void main9(String[] args) {
int x =10;
func1(x);
System.out.println(x);
}
//实验二
public static void func2(int[] array){//在栈上新分配一块array引用变量的空间,也指向main_func2中的{1,2,3,4}
array = new int[]{11,12,13,14};//这里在堆上新new了一块数组对象{11,12,13,14},array指向该对象
}
//这里有个重要的概念,在Java中,都是按值传递,地址也是值
public static void main_func2(String[] args) {
int[] array1={1,2,3,4};
func2(array1);//这里把array1的地址值传过去了,
System.out.println(Arrays.toString(array1));
}
// 输出结果为1 2 3 4
//实验三
public static void func3(int[] array){
array[0]=199;//array指向的下标为0的元素值改为199
}
public static void main_func3(String[] args) {
int[] arr = {100,99,88,77};
func3(arr);
System.out.println(Arrays.toString(arr));
}
// 输出结果为199,99,88,77
下面是本篇博客用到的源代码,需要回顾一下的可以参考参考。
import java.util.Arrays;
/**
* Created with IntelliJ IDEA.
* Description: Hello,I would appreciate your comments~
* User:
* Date: -03-20
* Destination:
* 1、如何定义数组?
* 2、如何初始化数组?
* 2、如何打印输出数组?
*/
public class arrDemo {
/**第四种拷贝方法
* array.clone();
* @param args
*/
public static void main_copy4(String[] args) {
int[] array = {1,2,3,4};
int[] copy = array.clone();
System.out.println(Arrays.toString(copy));
System.out.println(array);
System.out.println(copy);
// 输出的地址不一样,说明新开辟了一个引用变量之后,新new并且指向了一个对象
}
/**第三种拷贝方式
* 利用arraycopy
* @param args
*/
public static void main_copy3(String[] args) {
int[] array = {1,2,3,4};
int[] copy = new int[array.length];
System.arraycopy(array,0,copy,array.length,array.length);
System.out.println(Arrays.toString(copy));
}
/**第一种拷贝方法
* 利用Array类的从copyOf方法来进行拷贝(你要拷贝的数组,返回的数组长度)
* @param args
*/
public static void main_copy2(String[] args) {
int[] array = {1,2,3,4};
int[] copy = Arrays.copyOf(array,array.length);
int[] copy2 = Arrays.copyOf(array,2*array.length);
System.out.println(Arrays.toString(copy));
System.out.println(Arrays.toString(copy2));
// 其实是扩容
}
/**第一种拷贝方法
* 使用for进行拷贝
*/
public static void main_copy(){
int[] array = {1,2,3,4};
int[] copy = new int[array.length];
for (int i=0;i<array.length;i++){
copy[i] = array[i];
}
System.out.println(copy);
}
/**
* 题目1:这里写一个函数将一维数组以字符串的形式进行输出[1,2,3,4]
* @param array
* @return
*/
public static String myToString(int[] array){
String string="[";
for(int i =0;i<array.length;i++){
string+=array[i]+"";
if(i!=array.length-1){
string +=",";
}
}
string+="]";
return string;
}
public static void main_myToString(String[] args) {
int[] array = {1,2,3,4};
String ret = myToString(array);
System.out.println(ret);
}
public static void main(String[] args) {
int[] arr = {1,2,3};
func(arr);
System.out.println("arr[0] = " + arr[0]);
}
public static void func(int[] a) {
a[0] = 10;
System.out.println("a[0] = " + a[0]);
}
public static void func2(int[] array){//在栈上新分配一块array引用变量的空间,也指向main_func2中的{1,2,3,4}
array = new int[]{11,12,13,14};//这里在堆上新new了一块数组对象{11,12,13,14},array指向该对象
}
//这里有个重要的概念,在Java中,都是按值传递,地址也是值
public static void main_func2(String[] args) {
int[] array1={1,2,3,4};
func2(array1);//这里把array1的地址值传过去了,
System.out.println(Arrays.toString(array1));
}
// 输出结果为1 2 3 4
public static void func3(int[] array){
array[0]=199;//array指向的下标为0的元素值改为199
}
public static void main_func3(String[] args) {
int[] arr = {100,99,88,77};
func3(arr);
System.out.println(Arrays.toString(arr));
}
// 输出结果为199,99,88,77
public static void func1(int a) {
a=20;
}
public static void main9(String[] args) {
int x =10;
func1(x);
System.out.println(x);
}
public static void main8(String[] args) {
int a = 10;
int b = 20;
int[] arr = new int[]{1,2,3};
int [] array1 = {1,2,3,4};
array1 = new int[]{11,12,13,14};
array1 = new int[]{6,7,8,9};
}
// 对象 引用
public static void main7(String[] args) {
int[] array1 = {1,2,3,4};
int[] array2 = array1;
System.out.println("array1:" + Arrays.toString(array1));
System.out.println("array2:" + Arrays.toString(array2));
System.out.println("======================");
array2[0] = 99;
// 这样用array2改变首元素的值时,我们测试可以发现array1[0]也变了
System.out.println("array1:" + Arrays.toString(array1));
System.out.println("array2:" + Arrays.toString(array2));
int[] array3={1,2,3,4};
int[] array4={5,6,7,8};
array3=array4;
// 这个时候{1,2,3,4}这块内存就被JVM的垃圾回收机制回收了
System.out.println(Arrays.toString(array3));
System.out.println(Arrays.toString(array4));
}
public static void main6(String[] args) {
// 该方法用于了解引用变量
int[] array = {1,2,3};
double[] array2 ={1.0,2.0,3.0};
System.out.println(array);
System.out.println(array2);
int[] array3 = null;//代表array这个引用变量不指向任何对象
// 不能用int array3 = 0;来初始化引用变量
// 而且这个时候 System.out.println(array3);的话
// 会显示空指针异常
// System.out.println(array3[0]);也会显示空指针异常
// 既然引用变量为null了,那么就不能用array.length以及其他类似的用法了,会报错
}
public static void main5(String[] args) {
int a = 10;//局部变量=》存放到 栈【虚拟机栈】中
}
public static void main4(String[] args) {
// 该main方法解释如何打印输出数组内容
int[] array = {1,2,3};
for(int i=0;i<array.length;i++){
System.out.println(array[i]+" ");
}
// 第一种,普通for循环
for( int x : array ){
System.out.println(x+" ");
}
// 第二种for-each for循环的增强版
// 第三种,借助Java本身带有的一些方法来实现数组的打印Array
String ret = Arrays.toString(array);
// 把参数的数组转化成字符串进行输出
System.out.println(ret);
// 输出带有中括号是因为方法内部自动帮你包装了一下
System.out.println(Arrays.toString(array));
}
public static void main4_1(String[] args) {
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]);
}
public static void main3_4(String[] args) {
int[] array = {1,2,3};
System.out.println(array[3]);
}
public static void main3_3(String[] args) {
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]);
// 也可以通过[]对数组中的元素进行修改
array[0] = 100;
System.out.println(array[0]);
}
public static void main3(String[] args) {
int[] array ;
// 我们称这样为动态初始化
array = new int[]{1,2,3,4,5};
array = new int[]{9,8,7,6,5};
// 这样重新定义是可以的
int[] array2 = {1,2,3,4,5};
// 我们称这么做为静态初始化
//
// array2 = {9,8,7,6,5}这样写会报错,因为定义数组的时候整体赋值,只有一次机会
}
public static void main2(String[] args) {
double[] array = new double[10];
String[] strings = new String[10];
// 此时我们并没有初始化,但静态初始化虽然没有指定数组的长度,编译器会自动帮你确定
}
public static void main1(String[] args) {
int[] array = {1,2,3,4,5,6,7,8,9,10};
int ret = array[2];
System.out.println(ret);
// 下标默认从0开始
int[] array2 = new int[]{1,2,3,4,5,6,7,8,9,10};
// 两种是一样的,一般用第一种
int[] array3 = new int[10];
// 不确定里面放什么的话,就用第三种
}
}
就到这里吧,剩下数组的应用也是一块比较重要的内容,等我学透了再帮大家整理~
还有!千万不要在CSDN里面频繁使用ctrl z!这次真的吃大亏了/(ㄒoㄒ)/😣
感谢阅读!
版权归原作者 Gremmie102 所有, 如有侵权,请联系我们删除。