Java(六)——浅谈Java数组(二)

数组是一种引用类型,数组引用变量只是一个引用,数组元素和数组变量在内存中是分开存放的。下面将介绍数组在内存中的运行机制。

一、内存中的数组

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

与所有引用变量相同的是,引用变量是访问真实对象的根本方式。也就是说,如果希望在程序中访问数组对象本身,则只能通过这个数组的引用变量来访问它。

实际的数组对象被存储在堆(heap)内存中;如果引用该数组对象的数组引用变量是一个局部变量,那么它被存储在栈(stack)内存中。数组在内存中的存储示意图如下图所示。
这里写图片描述
如果需要访问上图中堆内存中的数组元素,则程序中只能通过p[index]的形式实现,也就是说,数组引用变量是访问堆内存中数组元素的根本方式。

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

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

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

int[] a = {5,7,20};
int[] b = new int[4];
// 此时b数组长度为4,元素均为0
// 接下来因为a、b均是int[]类型,所以可以将a的值赋给b
b = a;
// 再次输出b的长度,此时b的长度为3。
// 看起来似乎数组的长度是可变的,但这只是一个假象。

定义并初始化a、b两个数组后的存储示意图如下图:
这里写图片描述
当执行b=a;时,系统将会把a的值赋给b,a和b都是引用类型变量,存储的是地址。因此把a的值赋给b后,就是让b指向a所指向的地址。此时计算机内存的存储示意图如下图:
这里写图片描述

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

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

下面程序定义了一个int[]类型的数组变量,采用动态初始化的方式初始化该数组,并显式地为每个数组元素赋值。

// 定义一个int[]类型的数组变量
int[] iArr;
// 动态初始化数组,数组长度为5
iArr = new int[5];
// 采用循环方式为每个数组元素赋值
for(int i=0;i<iArr.length;i++) {
	iArr[i] = i + 10;
}

下面结合示意图详细介绍这段代码的执行过程。
执行第一行代码int[] iArr;时,仅定义一个数组变量,此时内存中的存储示意图如下:
这里写图片描述
执行第一行后,仅在栈内存中定义了一个空引用(就是iArr数组变量),这个引用并未指向任何有效的内存,当然无法指定数组长度。

当执行iArr = new int[5];动态初始化后,系统将负责为该数组分配内存空间,并分配默认的出示值:所有元素都被赋值为0,此时内存中的存储示意图如下图:
这里写图片描述
当循环为该数组的每个数组元素依次赋值后,此时每个数组元素的值都变成程序显式指定的值。此时存储示意图如下图:
这里写图片描述
从上图可以看到基本类型数组的存储示意图,每个数组元素的值直接存储在对应的内存中。操作基本类型数组的数组元素时,实际上相当于操作基本类型的变量。

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

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

接下来,我们通过一个例子,结合示意图说明引用类型数组的运行过程。

首先定义一个Person类

class Person {
	public int age; // 年龄
	public double height; // 身高

	// 构造器中初始化年龄、身高
	public Person(int age, double height) {
		this.age = age;
		this.height = heigth;
	}

	// 定义一个info方法
	public void info() {
		System.out.pirntln("我的年龄是:" + age
				+ ", 我的身高是:" + height);
	}
}

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

// 定义一个students数组变量,其类型是Person[]
Person[] students;
// 执行动态初始化
students = new Person[2];
// 创建两个Person实例,并将这两个Person实例分别赋值给tom变量和mary变量
Person zhang = new Person(15, 158);
Person lee = new Person(16, 161);
// 将tom变量赋值给第一个数组元素,mary变量赋值给第二个数组元素
students[0] = zhang;
students[1] = lee;
//下面两行代码的结果完全一样,因为mary和students[1]指向的是同一个Person实例
lee.info();
students[1].info();

执行Person students;代码时,定义一个students数组变量后的存储示意图:
这里写图片描述
执行动态初始化,系统为数组元素分配默认的初始值:null,即每个数组元素的值都是null。执行动态初始化后的存储示意图:
这里写图片描述
从上图可以看出,students数组的两个数组元素都是引用,而且这个引用并未指向任何有效的内存,因此每个数组元素的值都是null。这意味着依然不能直接使用students数组元素。这相当于定义了两个连续的Person变量,但这个变量还未指向任何有效的内存区,所以这两个连续的Person变量(students数组的数组元素)还不能使用。

接着的代码定义了zhang和lee两个Person实例,实际上分配了4块内存,在栈内存存储zhang和lee两个引用变量,在堆内存存储两个Person实例。此时students数组的两个数组元素依然是null,此时存储示意图如下图:
这里写图片描述
直到程序一次将zhang赋给students[0],将lee赋给students[1],students数组的两个数组元素将会指向有效的内存区。此时内存存储示意图如下图:
这里写图片描述
从上图可以看出,此时zhang和students[0]指向同一个内存区,而且它们都是引用类型变量,因此通过zhang和students[0]来访问Person实例的变量和方法的效果完全一样,不论修改Students[0]还是zhang所指向的Person实例的变量,所修改的其实是同一个内存区,所以必然互相影响。同理,lee和students[1]也是引用同一个Person对象,也具有相同的效果。

四、Java多维数组

Java语言里提供给了支持多维数组的语法,但是如果从数组底层的运行机制上来看,没有多维数组。

回到前面数组定义的语法格式来看:
type[] arrayName;
type是数组元素的类型,如果希望数组元素也是一个引用,而且是指向int数组的引用,那么可以把type具体成int[](前面已经指出int[]就是一种类型,其用法与普通类型并无任何区别),那么上面定义数组的语法就是int[][] arrayName;如果把int这个类型扩大到所有Java类型(不包括数组类型),则出现了定义二维数组的语法:
type[][] arrayName;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值