本节要点
- Java 数组的静态特性
- Java 数组的内存分配机制
- 初始化 Java 数组的两种方式
- 初始化基本类型数组的内存分配
- 初始化引用类型数组的内存分配
趁不是很忙的时候,读读书,多了解些 Java 底层相关的知识。
对于数组,确实是既熟悉又陌生,工作中经常用到,但其内存分配这些底层知识并不是很熟悉。一旦遇到内存泄露这种问题,估计一时半会儿也找不到具体的问题出在哪。
Java 数组在使用之前必须先对其进行初始化。
所谓初始化,即为数组对象的元素分配内存空间,并为每个数组元素指定初始值。
当数组的所有元素都被分配了合适的内存空间、并指定了初始值,初始化即完成。程序以后将不能重新改变数组对象在内存中的位置及大小。
从用法上看,数组元素相当于普通变量,程序既可以吧数组元素的值赋给普通变量,也可把普通变量的值赋给数组元素。
1.1 Java 数组的静态特性
Java 语言是典型的静态语言。
当数组被初始化后,其长度是不可变的,因此 Java 数组是静态的。
1.2 数组的初始化的两种方式及内存分配
1) 静态初始化
显式指定每个数组元素的初始值,由系统决定数组的长度。
2) 动态初始化
只指定数组长度,即为每个数组元素指定所需的内存空间,由系统为数组元素分配初始值。
注意:不要对数组同时使用静态初始化和动态初始化,即在初始化时,既指定长度,也为每个数组元素分配初始值。
不管用哪种方式,一旦初始化完成,数组元素的内存空间分配即结束,数组的长度就不可变,程序只能改变数组元素的值。
通过数组的 length 属性访问数组的长度。
数组在内存中的分配示例:
// 采用静态初始化方式初始化数组
String[] books = new String[]{"疯狂Java讲义",
"轻量级Java EE企业应用实战",
"疯狂Ajax讲义",
"疯狂XML讲义"
};
// 采用静态初始化方式的简化形式
String[] names = new String[]{"疯狂Java讲义",
"孙悟空",
"猪八戒",
"白骨精"
};
// 采用动态初始化方式初始化数组
String[] strArr = new String[5];
示例中3个变量及各自引用的数组在内存中的分配如图:
Java 的数组变量是一种引用类型的变量,数组变量并非数组本身,它只是指向堆内存中的数组对象(如上图)。因此可以改变一个数组变量所引用的数组:
books = names;
strArr = names;
此时,引用变量和数组对象在内存中的分配如下图:
从上图可以看出,已初始化的数组并没有改变,只是数组变量的引用指向改变而已。
所以,对于数组变量来说,它并不需要进行所谓的初始化,在使用该数组变量之前,只要让数组变量指向一个有效的数组对象即可。
需要进行初始化操作的是该引用变量所引用的数组对象。
同理,Java 中的对象引用变量也不需要进行初始化,需要进行初始化操作的是该引用变量所引用的对象。
1.3 基本类型数组的初始化
对于基本类型数组,Java 直接先为数组分配内存空间,再将数组元素的值存入对应的内存里。即数组元素的值是直接存储在对应的数组元素中的。
int[] iArr;
iArr = new int[]{2, 5, -12, 20};
内存中的存储如下图:
有些书中说“基本类型变量的值存放在栈内存中”,
准确的说应该是:所有局部变量(包括基本类型的变量、引用类型的变量)都是存放在栈内存,存储在各自的方法战区中;
引用类型变量所引用的对象(包括数组、Java 对象)则存储在堆内存中。
堆内存中的对象通常不允许直接访问,只能通过引用变量进行访问。
1.4 引用类型数组的初始化
引用类型数组的数组元素依然是引用类型的,因此元素里存储的还是引用,它指向另一块内存,该内存里存储了引用变量所引用的对象。
Person[] students = new Person[2];
students[0] = zhang;
students[1] = lee;
内存存储如下图:
另外,多维数组的本质依然是一维数组。
在使用数组时,应从内存控制的角度来把握程序,不要仅仅停留在代码表面。
参考资料:
疯狂Java:突破程序员基本功的16课-数组与内存控制