3.6 数组理解

一、内存中的数组

  数组引用变量只是一个引用,这个引用变量可以指向任何有效的内存,只有当该引用指向有效内存后,才可以通过该数组变量来访问数组元素。

实际的数组对象被存储在堆(heap)内存中;如果引用该数组对象的数组引用变量是一个局部变量,那么它被存储在栈(stack)内存中。数组在内存中的存储示意图:

  如果需要访问如图所示的堆内存中的数组元素,则程序只能通过p[index]的形式实现。即数组引用变量是访问堆内存中数组元素的根本方式。

堆内存和栈内存:

堆(heap)内存:Java虚拟机启动时分配一块永久的、很大的内存区。堆内存只有一块。

栈(stack)内存:每个方法运行时分配一块临时的、很小的内存区。每个方法都有自己的栈区,当方法结束时,对于的栈区就会被销毁。

  当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放到这个栈内存中,随着方法的执行结束,这个方法的内存栈也将随之销毁。因此,所有在方法中定义的局部变量都是在栈内存中的;在程序创建一个对象时,这个对象将被保存在运行时的数据区中,以便重复利用(因为对象的创建成本比较大),这个运行时数据区就是堆内存。堆内存的对象不会随着方法的结束而消失,即使方法结束后,这个对象还可以被其他另外一个引用变量所引用(在方法的参数传递中很常见),则这个对象依然不会被销毁。只有当一个对象没有任何变量引用它时,系统的垃圾回收器才会在合适的时候回收它。

   如果堆内存中的数组不在有任何引用变量指向自己,则这个数组将会成为垃圾,该数组所占的内存也会被系统的垃圾回收器回收。因此为了让垃圾回收器回收一个数组所占的内存空间,可以将该数组便便赋值为null,也就切断数组引用变量和实际数组之间的引用关系,实际的数组也就成为垃圾。

  只要类型相互兼容,就可以让一个数组变量指向另一个实际的数组,这样会让人产生数组长度可变的错觉。 

 1 class ArrayTest 
 2 {
 3     public static void main(String[] args) 
 4     {
 5         //静态方法定义数组
 6         int[] a={1,3,5};
 7         //动态初始化定义数组
 8         var b=new int[4];
 9         
10         System.out.println("数组b的长度为:"+b.length);
11         //循环输出数组a,b
12         for(int i:a)
13         {
14             System.out.print("  "+i);
15         }
16         System.out.print("\n");
17         for(int i=0;i<b.length;i++)
18         {
19             System.out.print("  "+b[i]);
20             if(i==b.length-1)
21                 System.out.print("\n");
22         }
23         //因为a、b都是int[]引用类型,所以可以让b指向a引用指向的数组
24         b=a;
25         System.out.println("指向a指向的数组后引用变量b指向的数组长度:"+b.length);
26     }
27 }
28 ---------- 运行java(捕获窗口) ----------
29 数组b的长度为:4
30   1  3  5
31   0  0  0  0
32 指向a指向的数组后引用变量b指向的数组长度:3
33 
34 输出完成 (耗时 0 秒) - 正常终止

 在变量与b变量都引用了第一个数组后,此时在堆内存的原数组元素失去了引用,变成了垃圾,等待垃圾回收器来回收它。

扫描二维码关注公众号,回复: 9204296 查看本文章

二、基本类型的数组的初始化

  对于基本类型的数组而言,数组元素的值直接存储在对应的数组元素中,因此,初始化数组时,先为该数组分配内存空间,然后直接将数组元素的值存入对应数组元素中。

class PrimitiveArrayTest 
{
    public static void main(String[] args) 
    {
        //定义一个int[]类型的数组变量
        int[] array;
        //动态初始化数组,数组长度为5
        array=new int[5];
        //采用循环方式为数组每个元素赋值
        for(var i=0;i<array.length;i++)
        {
            array[i]=i+10;
        }
        //循环输出数组
        for(int i:array)
        {
            System.out.println(i);
        }
    }
}
---------- 运行java(捕获窗口) ----------
10
11
12
13
14

输出完成 (耗时 1 秒) - 正常终止

 执行int[] array,此时分配一个栈内存和一个堆内存(java虚拟机启动的时候就有),定义了一个空引用,这个引用变量并未指向任何有效的内存,也就无法知道数组的长度

 array=new int[5],动态初始化后,系统将为该数组分配内存存储空间,并分配默认初始化值:所有数组元素赋值为0。

        for(var i=0;i<array.length;i++)
        {
            array[i]=i+10;
        }
循环为数组赋值

三、引用类型的初始化

基本类型的赋值:直接将该值存入变量的内存中。

引用类型的赋值:将该引用对象的第一个内存单元的编号(首地址)存入变量。

引用类型数组的数组元素时引用,因此情况更加复杂。每个数组元素里存储的还是引用,它指向另一块内存,这块内存里存储了有效数据。

定义一个Person类

 1 class Person 
 2 {
 3     public int age;
 4     public double height;
 5     //定义一个方法
 6     public void info()
 7     {
 8         System.out.println("年龄:"+age+",身高:"+height);
 9     }
10 }

 下面程序定义一个Person[]数组,接着初始化这个Person[]数组,并为这个数组的每个元素指定值。

 1 public class ReferenceArrayTest
 2 {
 3     public static void mian(Srting[] args)
 4     {
 5         //定义一个sudentd数组,类型Person[]
 6         Person[] students;
 7         //执行初始化
 8         students=new Person[2];
 9 
10         //创建两个Person实例
11         var zhang=new Person();
12         zhang.age=15;
13         zhang.height=158;
14 
15         var lee=new Person();
16         lee.age=16;
17         lee.height=168;
18 
19         students[0]=zhang;
20         students[1]=lee;
21 
22         //下面代码的执行结果完全一样
23         lee.info();
24         students[0].info();
25     }
26 }

 执行Person[] students;代码时,这行代码仅仅在栈内存中定义了一个引用变量,也就是一个指针,这个指针并未指向任何有效内存区。

执行动态初始化,默认由系统指定初始值null。

 students=new Person[2];

 然后代码定义了zhang和lee两个Person实例,实际上分配了四个内存,在栈内存储zhang和lee两个引用变量,还在堆内存储两个Person实例,如下所示:

  students[0]=zhang;
  students[1]=lee;

  从图可以看出zhang和students[0]同时指向一个内存区,且都为引用变量,因此通过zhang和students[0]来访问Person实例的方法和变量效果是一样的,无论修改students[0]指向的实例还是zhang指向的实例,所修改的其实是一个内存区。

四、没有多维数组

  java语言的数组类型还是引用类型,这个引用指向真实的数组内存。数组元素的类型也可以是引用,如果数组元素的引用再次指向真实的数组内存,这种情况看上去就很像多维数组。

  定义一个变量的用法:type varName,type是变量类型。如果定义一个引用变量的数组类型,只需要将这个type具体成int[]。

  二维数组的定义:

type[][] arrayName;

从采用上面的语法格式来定义二维数组,但它的实质还是一维数组,只是数组元素还是引用,数组元素里保存的引用指向一位数组。

对二维数组的初始化,同样可以将数组看成以为数组初始化,把这个“二维数组”当成一一维数组,其元素类型是type[]类型,可以采用以下语法进行初始化

arrayName=new type[length][]

上面的语法相当于初始化一个一维数组,这个一维数组的长度为length,同样这个一维数组的数组元素是引用类型(数组类型),所以系统为每个元素分配的初始值为null。这个二维数组完全可以当成一维数组:使用new type[length]相当于定义了length个type类型变量;类似的,使用new type[length][]初始化这个数组后,相当于定义了length个type[]类型的变量,当然这个type[]类型的变量都是数组类型,因此需要再一次进行初始化。

 1 class 二维数组
 2 {
 3     public static void main(String[] args)
 4     {
 5         //定义一个二维数组
 6         int[][] a;
 7         //把a当成一维数组进行初始化,初始化a为一个长度为4的数组
 8         //a数组的数组元素有是引用类型
 9         a=new int[4][];
10         //把a当成一维数组,遍历a数组的每个元素
11         for(int i=0;i<a.length;i++)
12         {
13             System.out.println(a[i]);
14         }
15         //初始化a数组的第一个元素
16         a[0]=new int[2];
17         a[0][0]=1;
18         a[0][1]=2;
19 
20         //遍历二维数组a的第一个元素
21         for(int i=0;i<a[0].length;i++)
22         {
23             System.out.println(a[0][i]);
24         }
25     }
26 }
[][] a;这一行代码,将在栈里定义一个引用变量,这个变量并未指向任何有效的内存空间,此时堆内存还未为这行代码分配任何存储区。
a=new int[4][];程序对数组进行初始化,这行代码让a变量指向一块长度为4的内存空间,这个长度为4的数组里每个数组元素都是引用类型(数组类型),系统为这些数组元素分配初始值:null。此时数组a在内存中存储的示意图:

 a[0]=new int[2];
 a[0][0]=1;
 a[0][1]=2;

对一个数组原素a[0]进行初始化

 初始化多维数组时,可以只指定左边维的大小;当然也可以一次指定每一维的大小。

1 int[][] b=new int[3[4];

上面的代码定义一个b数组变量,这个变量指向一个长度为3的数组,这个数组的每个元素又是一个数组类型,他们各自指向长度为4的int[]数组,每个数组的元素为0。示意图为:

还可以使用静态初始化方式来初始化二维数组,使用静态初始化方式来初始化二维数组,二维数组的每个数组元素都是一维数组,因此必须指定多个一维数组作为二维数组的初始值。代码如下:

//使用静态初始化方法来初始化一个二维数组
String[][] str1=new String[][]{new String[3],new String[]{"hello"}}

 结论:二维数组是一维数组,其数组元素是一维数组;三维数组其数组元素是二维数组。

猜你喜欢

转载自www.cnblogs.com/weststar/p/12314117.html
3.6