• 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("
    ");
    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("
    ");
    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变量都引用了第一个数组后,此时在堆内存的原数组元素失去了引用,变成了垃圾,等待垃圾回收器来回收它。

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

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

    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"}}

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

  • 相关阅读:
    word 操作技巧
    有朝一日
    菜狗日记2021.7.10
    记一次JSON.toJSONString()踩坑
    菜狗日记2020.11.13
    菜狗日记2020.11.6
    菜狗日记2020.11.03
    菜狗日记2020.10.29
    菜狗日记2020.10.17
    菜狗日记2020.10.11
  • 原文地址:https://www.cnblogs.com/weststar/p/12314117.html
Copyright © 2020-2023  润新知