数组的定义与使用

1.数组的基本概念

1.1为什么需要数组
不接触数组之前,我们存储数据一般都是一个一个去存储,一个一个的去输出,比如像存储学生的考试成绩,我们会这么去写代码:

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);
   }
}

如果需求是10个,20个我也许还可以写写,但是如果是需要存储100、1000个甚至更多的情况之下,那我们就存储下来的效率值会大大下降,通过观察这些数据的基本类型是一样的,来存储这么多相同的数据类型,我们可以用数组来解决这样的问题。
1.2什么是数组
数组:可以看成是相同类型元素的一个集合。在内存中是一段连续的空间。
数组的定义与使用_第1张图片

数组在内存存放数据像上面的图一样,存储数据在内存中时连续的。从图中可以看到:

  1. 数组中存放的元素其类型相同
  2. 数组的空间是连在一起的
  3. 每个空间有自己的编号,其实位置的编号为0,即数组的下标

那么在程序中怎么去创建这个数组呢?

1.3数组的创建及初始化
基本格式:

数据类型 [] 数组名 = new 数据类型 [] {存储的内容};

我们以整型和double类型举例:

int [] array = new int [] {12345}//整型数组
double [] dd = new double [] {2.0,4.5,3.7};//double类型数组

数组的创建c语言也JavaSE不一样,在javaSE中数组的创建可以写成三种形式:
我们都以整型数组来举例:

int [] array ={1,2,3,4,5};//精简形式
int [] array = new int [] {1,2,3,4,5};//基本形式
int [] array = new int [5];//给定数组大小的形式 

这三种形式中,在创建数组的时候你都可以使用。我个人推荐使用第一种,因为比较方便,还有在使用这三种形式的时候只有第三种的[]可指定大小,其他都不可以。
最后在创建数组的写法还有一种:参考c语言的写法:

int array [] = {1,2,3,4,5};//在java中我们不推荐这么去创建数组。

因为本来c语言这么写是存在问题的,我们举一个例子:
像基本类型创建:

int a = 10;
double b= 3.14;
char c = 'z';

这些基本类型在创建的时候都是以 数据类型 变量名 = 存储的内容; 这样的基本格式来写的。
在数组中(我们以整型数组举例):int [] 这个就是基本类型 array这个就是数组名 。按照规则来说,像java这样来写是符合规则的,c语言那样写是不符合规则的。

如果没对数组初始化,数组元素有默认值
如果存储的是基本类型,默认值为基本类型对应的默认值,比如:

类型 默认值
byte 0
int 0
short 0
long 0
float 0.0f
double 0.0
char /u0000
boolean false

举一个int类型和boolean类型的例子:
代码实现:

public class Test {
    public static void main(String[] args) {
        int [] array = new int[5];
        boolean [] boo = new boolean[5];
    }
}

在调试下的结果确实和上方表格的默认值一样。
数组的定义与使用_第2张图片
如果数组中存储的是引用类型,默认值为null。
我们可以创建一个String类型的数组:

public static void main(String[] args) {
        String [] str = new String[5];
    }

在监视下的结果显示,默认值就是null。
在这里插入图片描述
1.4数组的使用
1.4.1 数组中元素访问
数组在内存中是一段连续的空间,空间的编号都是从0开始的,依次递增,该编号称为数组的下标,数组可以通过下标访问其任意位置的元素。比如:

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]);

【注意事项】

  1. 数组是一段连续的内存空间,因此支持随机访问,即通过下标访问快速访问数组中任意位置的元素
  2. 下标从0开始,介于[0, N)之间不包含N,N为元素个数,不能越界,否则会报出下标越界异常
int[] array = {1, 2, 3};
System.out.println(array[3]); // 数组中只有3个元素,下标一次为:0 1 2,array[3]下标越界

在这里插入图片描述

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException//数组下标越界异常

在访问数组的时候一定要谨慎数组越界。
1.4.2 遍历数组
所谓 “遍历” 是指将数组中的所有元素都访问一遍, 访问是指对数组中的元素进行某种操作,比如:打印。
有三种方式可以去打印:

public static void main(String[] args) {
        int [] array = {1,2,3,4,5};
        //通过输出一个一个的输出。
        System.out.print(array[0]+" ");
        System.out.print(array[1]+" ");
        System.out.print(array[2]+" ");
        System.out.print(array[3]+" ");
        System.out.println(array[4]+" ");
        //通过循环将数组中的内容一一打印出来
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]+" ");
        }
        System.out.println();
        //通过Arrays类中的toString()方法来打印(将数组的内容转化为字符串再输出)
        System.out.println(Arrays.toString(array));
    }

打印的结果如下:
数组的定义与使用_第3张图片
在上面三种方式中第三种方式用Arrays类中的toString方法,在Arrays类的toString方法中不仅仅可以将整型数组中的内容转化为字符串的形式去输出,还有以下类型:
数组的定义与使用_第4张图片
注意:在数组中可以通过 数组对象.length 来获取数组的长度

在java中还有一个方式可以遍历数组的内容,那就是for-each:

//for (数组中数据的类型 存储返回的值的变量名:数组名) {
        //    语句体;
        //}

用for-each来遍历这个数组

public static void main(String[] args) {
        int [] array = {1,2,3,4,5,6};
        for (int x:array) {
            System.out.print(x+" ");
        }
    }

结果如下:
在这里插入图片描述
用这个方式去遍历有什么缺点呢?
灵活性不足,这个方式一遍历就将数组中的内容全遍历出来。
这个方式也有优势:
可以避免循环条件和更新语句写错。

2.数组是引用类型

2.1初始JVM的内存的分布
数组在内存存储是一段连续的存储空间,主要用来存储程序运行时数据的。比如:

  1. 程序运行时代码需要加载到内存
  2. 程序运行产生的中间数据要存放在内存
  3. 程序中的常量也要保存
  4. 有些数据可能需要长时间存储,而有些数据当方法运行结束后就要被销毁

因此JVM也对所使用的内存按照功能的不同进行了划分:
数组的定义与使用_第5张图片

程序计数器 (PC Register): 只是一个很小的空间, 保存下一条执行的指令的地址

虚拟机栈(JVM Stack): 与方法调用相关的一些信息,每个方法在执行时,都会先创建一个栈帧,栈帧中包含有:局部变量表、操作数栈、动态链接、返回地址以及其他的一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。

本地方法栈(Native Method Stack): 本地方法栈与虚拟机栈的作用类似. 只不过保存的内容是Native方法的局部变量. 在有些版本的 JVM 实现中(例如HotSpot), 本地方法栈和虚拟机栈是一起的

堆(Heap): JVM所管理的最大内存区域. 使用 new 创建的对象都是在堆上保存 (例如前面的 new int[]{1, 2,3} ),堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销毁。

方法区(Method Area): 用于存储已被虚拟机加载

现在我们只简单关心堆 和 虚拟机栈这两块空间.
2.2基本类型变量与引用类型变量的区别
基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的是其所对应的值;
而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址。

基本类型:

int a = 10;

像基本类型存放的内容是直接存储在虚拟机栈中,以画图的形式展示:
数组的定义与使用_第6张图片
引用类型:

int [] array = new int [] {1,2,3,4,5};

引用类型存放的内容是存储在堆上,通过在栈上创建一个引用变量,通过引用变量指向堆上的对象,以画图的形式展示:
数组的定义与使用_第7张图片
像c语言中,基本类型传参到函数中,传地址可以改变变量存放的值,但在java中是做不到的,因为根本拿不到这个变量存放内容的这个地址。在java中引用类型可以通过地址去找到存放的内容,并且可能可以改变这个存放的内容。
2.3认识null
null 在 Java 中表示 “空引用” , 也就是一个不指向对象的引用。

public static void main(String[] args) {
        int [] array = null;
        System.out.println(array[0]);
    }

null 的作用类似于 C 语言中的 NULL (空指针), 都是表示一个无效的内存位置. 因此不能对这个内存进行任何读写操作. 一旦尝试读写, 就会抛出 NullPointerException.
在这里插入图片描述
注意: Java 中并没有约定 null 和 0 号地址的内存有任何关联。

3. 数组的应用场景

3.1 保存数据

public static void main(String[] args) {
int[] array = {1, 2, 3};
for(int i = 0; i < array.length; ++i){
System.out.println(array[i] + " ");
}

3.2 作为函数的参数

  1. 参数传基本数据类型
 public static void fun(int n) {
        n=10;
        System.out.println("n="+n);
    }
    public static void main(String[] args) {
        int num = 0;
        fun(num);
        System.out.println("num="+num);
    }

因为基本类型存储在栈上,传参过去也不会改变num的值,n只会重新在栈上开辟一个新的空间,并且当出了fun这个方法n开辟的空间也会被回收。
数组的定义与使用_第8张图片
2. 参数传数组类型(引用数据类型)

  public static void fun1(int [] array) {
        array[2]=100;
    }
    public static void main(String[] args) {
        int [] array = {1,2,3,4,5};
        fun1(array);
        System.out.println(Arrays.toString(array));
    }

在这里插入图片描述
数组的定义与使用_第9张图片
通过上面的运行结果和画图,在main方法中先创建引用变量array通过地址指向堆上的对象,然后通过传参到fun1方法上,也创建了一个array引用变量,这个过程相当一个赋值将main方法中的array引用变量的地址赋给了fun1方法中创建的array引用变量上,然后通过这个地址指向了堆上的对象,这样这两个引用变量都指向同一个对象,然后进行到fun1方法中这个程序array[2]=100;将原来堆上的下标为2的数改为100,当这个fun1方法结束之后,由这个fun1方法创建的引用变量array也被系统回收了。然后执行System.out.println(Arrays.toString(array));这一段代码,将数组的内容打印出来,最后当main方法也结束之后main方法中的array引用变量也被回收,堆上的对象所储存的内容也被销毁回收。

总结: 所谓的 “引用” 本质上只是存了一个地址. Java 将数组设定成引用类型, 这样的话后续进行数组参数传参, 其实只是将数组的地址传入到函数形参中. 这样可以避免对整个数组的拷贝(数组可能比较长, 那么拷贝开销就会很大).

在引用变量传参不一定可以改变实参的内容:

   public static void fun2(int [] array) {
        array = new int [] {11,22,33,44,55};
    }
    public static void main(String[] args) {
        int [] array = {1,2,3,4,5};
        fun2(array);
        System.out.println(Arrays.toString(array));
    }

在这里插入图片描述
这个传参过去就没有改变数组的内容,我们来通过画图来分析一下:
数组的定义与使用_第10张图片
在fun2方法中创建的array引用变量重新指向了一个在堆上的新对象,这样改变不了main方法中的内容。
总结:传参传引用变量不一定会改变实参的内容。

3.3 作为函数的返回值
比如:获取斐波那契数列的前N项

public static  int [] fib(int n) {
        if(n <= 0) {
            return null;
        }
        int [] array = new int [n];
        array[0] = array[1] = 1;
        for (int i = 2; i <n ; i++) {
            array[i] = array[i-1]+array[i-2];
        }
        return array;
    }
    public static void main(String[] args) {
         int [] array = fib(10);
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]+" ");
        }
    }

在这里插入图片描述
本章完…

你可能感兴趣的:(java,java)