继续打捞站内信。
[quote="同学F"]在java中的数组访问,举个例子,对于数组int[][][] arry = new int[2][3][4],我从字节码上看,虚拟机对某个arry中的某个元素如arry[1][1][3]的访问,似乎是先获取arry[1]的引用,然后再获取arry[1][1]的引用,再获取数据arry[1][1][3],如果这个过程我没有理解错的话,那么虚拟机是不是对这些“中间引用”(arry[1]、arry[1][1]之类的)创建相应的类型,否则单凭这些引用如何进行数组下表的越界校验?[/quote]
Java和JVM里本来就没有所谓的“矩形数组”的概念,多维数组只有“数组的数组”(array-of-arrays)或者叫jagged array。
与之对比,C#和CLI里就有真正的多维“矩形”数组,也支持“数组的数组”。关于几种不同的语言里多维数组的差异,可以参考[url=http://rednaxelafx.iteye.com/blog/317038]以前一帖[/url]。
也就是说,在Java里
[table]| [b]类型[/b] | [b]说明[/b] |
| int | 这是一个基本类型 |
| int[] | 这是以int为元素的数组类型 |
| int[][] | 这是以int[]为元素的数组类型 |
| int[][][] | 这是以int[][]为元素的数组类型 |
[/table]
一个数组类型的“组件类型”([url=http://download.oracle.com/javase/6/docs/api/java/lang/Class.html#getComponentType()]component type[/url])就是该数组类型的维度(dimension)减去1的类型;字面上看也就是少一对[]。
每个维度上都是一个真正的数组对象。每个数组对象都记录着自己的长度(length)。所以对每个数组对象都可以用arraylength指令去查询它们的长度,每个Xaload / Xastore也就可以做相应的边界检查。
int[][][] array = new int[2][3][4];
这只是个简写而已。虽然这个语句的右手边对应与一组JVM字节码指令,
0: iconst_2
1: iconst_3
2: iconst_4
3: multianewarray #2, 3; //class "[[[I"
实际上它的作用[b]大致[/b]等同于:
int[][][] a = new int[2][][];
for (int i = 0; i < a.length; i++) {
a[i] = new int[3][];
for (int j = 0; j < a[i].length; j++) {
a[i][j] = new int[4];
}
}
以32位HotSpot VM的实现为例,上面两个版本的代码创建出来的对象都是这种样子的:
[img]http://dl.iteye.com/upload/attachment/571934/aa6c6ae0-67e4-3ae8-a763-d691b5edabe9.png[/img]
这样就好理解了吧?
可以参考JVM规范去了解multianewarray的语义:
[url]http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc9.html#multianewarray[/url]
HotSpot VM的实现里,参考解释器版的实现比较直观,在这里:
[url=http://hg.openjdk.java.net/jdk6/jdk6/hotspot/file/tip/src/share/vm/interpreter/interpreterRuntime.cpp]interpreterRuntime.cpp[/url]: InterpreterRuntime::multianewarray
[url=http://hg.openjdk.java.net/jdk6/jdk6/hotspot/file/tip/src/share/vm/oops/objArrayKlass.cpp]objArrayKlass.cpp[/url]: objArrayKlass::multi_allocate
[url=http://hg.openjdk.java.net/jdk6/jdk6/hotspot/file/tip/share/vm/oops/typeArrayKlass.cpp]typeArrayKlass.cpp[/url]: typeArrayKlass::multi_allocate
该指令接受两个参数,
第一个是多维数组的类型,这个例子是里[[[I,也就是int[][][];
第二个是多维数组的维度n,这个例子里n=3。
知道了维度之后,JVM执行这条指令时就会从操作数栈顶弹出n个值(必须都是int型),并以这些int为每个维度的length嵌套循环的去创建数组对象出来;这个例子里也就是把前面iconst_2、iconst_3、iconst_4指令压到操作数栈上的常量2、3、4分别弹出来,并且作为数组的各维度的长度去创建实例。
multianewarray与上面写的那种显式用嵌套循环来初始化多维数组的Java代码最大的差异是,multianewarray会检查所有维度上的length是否非负,如果有负数就会抛[url=http://download.oracle.com/javase/6/docs/api/java/lang/NegativeArraySizeException.html]NegativeArraySizeException[/url];要注意的是[color=red]无论传入的多维数组是否有维度的长度是0,所有维度都会被检查[/color]。
而显然,如果显式用嵌套循环来初始化的话,负数长度的问题就有可能“逃过去”。
看例子:
public class Foo {
public static void main(String[] args) {
int[][][] array = new int[1][0][-1];
}
}
这个会抛NegativeArraySizeException异常:
$ java Foo
Exception in thread "main" java.lang.NegativeArraySizeException
at Foo.main(Foo.java:3)
而
public class Bar {
public static void main(String[] args) {
int[][][] a = new int[1][][];
for (int i = 0; i < a.length; i++) {
a[i] = new int[0][];
for (int j = 0; j < a[i].length; j++) { // a[i].length == 0
a[i][j] = new int[-1]; // 这个循环体不会被执行,-1的长度就被“跳过去”了
}
}
}
}
这个不会抛异常。