小白Java笔记——深入数组

来源:互联网 发布:卓行信贷网络借贷 编辑:程序博客网 时间:2024/06/01 21:50

前言:前面说过数组是一个引用数据类型,数组引用变量只是一个引用,数组元素和数组变量是分开存放的。正如定义数组的时候,只是定义一个引用变量,还需要再初始化。

一、内存中的数组

  • 数组引用变量(就是一开始定义的)只是一个引用,它可以指向任何有效的内存(只要你给他初始化,说明它需要指向哪里)。换句话说,只有引用变量指向有效的内存空间,才可以通过该数组变量来访问数组元素(也就是内存里的内容)。
  • 与所有引用变量相同的是,引用变量是访问真是对象的根本方式(如果想访问数组本身,只能通过这个数组的引用变量来访问它)。
  • 关于数组的储存:实际的数组对像储存在堆(heap)内存中;如果引用该数组对象的数组引用变量是一个局部变量,则存储在栈(stack)内存中,如图:
    这里写图片描述
    也就是如果要访问图中的数组P,就只能通过P[index]的形式实现。

  • 关于堆内存和栈内存详情看https://www.cnblogs.com/SaraMoring/p/5687466.html
    简单理解看下面:

关于栈内存和堆内存:
栈内存:用于一些基本类型的变量数据和对象的引用变量的存储;
堆内存:存放由new创建的对象和数组。
1 . 栈内存:当一个方法执行,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块内存栈中,随着方法的执行结束,这个内存栈也将自然销毁。也就是所有方法中定义的局部变量都放在栈内存中;
2 . 堆内存:在程序中创建一个对象时,将被保存在运行时数据区中,也就是堆内存中。堆内存中的对象不会随着方法的的结束而销毁,因为即使方法结束,该对象还可能会被另一个引用变量应用(方法的参数传递常见)。只有当这个对象没有被任何引用变量引用时,系统的垃圾回收才会在适合的时候回收它。

  • 由堆内存可以知道,如果想让垃圾回收机制回收一个数组所占的内存空间,可以将数组的变量赋值为null,也就切断了数组引用变量和实际数组之间的联系。
  • 只要类型相互兼容,就可以让一个数组变量指向另一个实际的数组,会让人产生数组的长度可变的错觉。
public class ArrayInRam{  public static void main(String[] args){   //静态   int[] a = {5,7,20};   //动态   int[] b = new int[4];   System.out.println("b长度是:"+ b.length);   for(int i=0,len=a.length;i<len;i++){     System.out.println(a[i]);}   for(int i = 0,len = b.length;i<len;i++){     System.out.println(b[i]); }   //因为a,b都是int[]类型,所以可以将a的值赋给b;   //也就是让b引用指向a引用引用指向的数组   b = a;   System.out.println("b的长度:"+ b.length);}}

结果:

b长度是:457200000b的长度:3

示意图:
这里写图片描述
这里写图片描述

  • 看待数组,一定要看成两部分:一个是定义的数组引用变量(例子中的a,b);另一个实际的数组对象(int[]),这一部分是在堆内存运行,通常无法直接使用,只能通过引用变量访问。

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

int[] iArr;iArr = new int[4];for(int i = 0 ; i <iArr.length ; i++){ iArr[i] = i + 10;}

过程图:
这里写图片描述
可以看到定义时,引用变量iArr并没有指向堆内存,因为没有初始化;
这里写图片描述
这里写图片描述
上图每个数组元素直接储存在对应的内存中,也就是说操作基本类型数组的数组元素时,实际上相当于操作基本类型的变量。

三、引用类型数组的初始化

  • 引用类型数组的元素是引用,每个数组元素里存储的是引用,它指向另一块内存,这个内存里存储了有效数据。
//首先定义一个Person类(所有类都是引用类型,person类想当一个模子)class Person{ public int age; public double height; //注意() public void info(){   System.out.println("我的年龄:"+age+"我的身高:"+height);}}//下面将定义一个Person[]数组,只不过这个数组引用变量名字不是Person,而是student,并为这个数组的每个元素指定值(这个值就是实例化的Person)public class ReferenceArrayTest{ public static void main(String[] args){  //定义一个student数组变量,类型是Person[]  Person[] students;  students = new Person[2];  //创建person实例,并将这个实例赋给zhang变量  Person zhang = new Person();  zhang.age = 15;  zhang.height = 158;  //创建person实例,并将这个实例赋给lee变量  Person lee = new Person();  lee.age = 23;  lee.height = 180;  //下面开始将zhang、lee的值赋给数组元素  students[0] = zhang;  students[1] = lee;  //下面两行代码结果一样  lee.info();  students[1].info();}}

结果:

我的年龄:23我的身高:180.0我的年龄:23我的身高:180.0

过程图:
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

  • 由图可以看出,最后两行代码结果一样,因为zhang和students[1]变量引用指向同一个内存区。如果修改students[1]所指向的person实例的实例变量,或者是zhang的,所修改的都是同一个内存变量,都会互相影响。

四、没有多维数组

java语言支持多维数组的语法,但如果从数组底层的运行机制来看——没有多维数组;Java语言里数组类型是引用类型,所以数组变量是一个引用,如果数组元素也是引用,指向真实内存,这种形式看起来像多维数组。

public class TwoDimesionTest{ public static void main(String[] args){  //定义一个二维数组,可以看成类型为int[]的一维数组  int[][] a;  //把a当成一维数组,初始化长度是4  //a数组元素又是引用类型,也就相当于4个一维数组  a = new int[4][];  //当成一维数组,遍历  for(int i = 0 , len = a.length; i < len ; i++)   {     System.out.println(a[i]);  }   //初始化数组第一个元素   //理解:上面只是动态初始化了整个a数组  a[0] = new int[2];  //访问第一个元素所指的第二个元素  a[0][1] = 6;   for(int i = 0 , len = a[0].length; i < len ; i++)   {     System.out.println(a[0][i]);  }}}

结果:

nullnullnullnull06

过程图:
这里写图片描述
这里写图片描述

注意:图中a[0]的元素,不能再指向另一个数组。因为Java是强类型语言,前面已经定义了数组元素是int[]类型,所以a[0]中的数组元素只能是int类型。
想实现无限扩展的数组,可以定义一个Object[]类型的数组。

//一、动态初始化另一种写法:同时初始化二维数组的两个维数int[][] b = new int[3][4];//二、静态方法来初始化,指定多个一维数组作为二维数组的初始化值String[][] str1 = new String[]{new String[3],new String[]{"Hello!"}};//更简化String[][] str2 = {new String[3],new String[]{"Hello!"}};

五、Java 8增强的工具类:Arrays
Java提供Arrays类里包含一些static修饰的方法可以直接操作数组,包含如下几个static修饰方法(通过类名调用):

  • int binarySearch(type[] a,type key):用二分法查询key元素值在数组a的索引;如果不包含key