</p> 呵呵,不美观真是JVM这家伙自个偷偷建树了[I类。JVM不把数组类放到任何包中,也不给他们起个正当的标识符名称,估量是为了避免和JDK、第三方及用户自界说的类发生冲突吧。0 P) A3 T% T' e0 O
再想想,JVM也必需动态生成数组类,因为Java数组类的数目与元素类型、维度(最多255)有关,相当相当多了,是没法预先声明好的。
" V# \9 J. \' d8 D, v1 ~ 居然没有length这个成员变量!0 c8 Z$ t' X- r# {2 e8 R
我们已经发现,偷懒的JVM没有为数组类生成length这个成员变量,那么Array.length这样的语法若何经由过程编译,若何执行的呢?
$ y( Q% S3 {% ~* A | 让我们看看字节码吧!编写一段最简单的代码,使用jclasslib查看字节码。/ R+ i3 h, |/ z
public class Main { public static void main(String[] args) { int a[] = new int[2]; int i = a.length; } } 使用SUN JDK 1.6编译上述代码,并使用jclasslib打开Main.class文件,获得main体例的字节码:
; M3 S- w. S0 Q# Z 0 iconst_2 //将int型常量2压入操作数栈 1 newarray 10 (int) //将2弹出操作数栈,作为长度,建树一个元素类型为int, 维度为1的数组,并将数组的引悠揭捉入操作数栈 3 astore_1 //将数组的引用年夜操作数栈中弹出,保留在索引为1的局部变量(即a)中 4 aload_1 //将索引为1的局部变量(即a)压入操作数栈 5 arraylength //年夜操作数栈弹出数组引用(即a),并获取其长度(JVM负责实现若何获取),并将长度压入操作数栈 6 istore_2 //将数组长度年夜操作数栈弹出,保留在索引为2的局部变量(即i)中 7 return //main体例返回 可见,在这段字节滤鱿脯根柢就没有看见length这个成员变量,获取数组长度是由一条特定的指令arraylength实现(怎么实现就不管了,JVM 总有法子)。编译器对Array.length这样的语法做了非凡措置,直接编译成了arraylength指令。此外,JVM建树数组类,应该就是由 newarray这条指令触发的了。
" a: W& o$ G4 y5 Y3 E" w0 r% s 很自然地想到,编译器也可以对Array.length()这样的语法做非凡措置,直接编译成arraylength指令。这样的话,我们就可以使用体例挪用的气概获取数组的长度了,这样看起来貌似也加倍OO一点。那为什么不使用Array.length()的语法呢?也许是开发Java的那帮天才对.length有所偏幸,或者抛硬币拍脑壳随便抉择的吧。 形式不主要,主要的是我们年夜白了背后的机理。+ T% r6 d! ]7 ^
Array in Java
; E* s- S$ H% _7 n. h: h( P- J) [ 最后,对Java中纯对象的数组揭晓点感应吧。) T( @# c9 O3 |$ D. X: @' _3 t
对比C/C++中的数组,Java数组在平安性要好良多。C/C++常碰着的缓存区溢出或数组访谒越界的问题,在Java中不再存在。因为Java使用特定的指令访谒数组的元素,这些指令城市对数组的长度进行搜检。如不美观发现越界,就会抛出java.lang.ArrayIndexOutOfBoundsException。4 ~; Q' f- k8 z3 I9 M$ ^7 K
Java数组元素的矫捷性斗劲年夜。一个数组的元素自己也可所以数组,只要所有元素的数组类型不异即可。我们知道数组的类型和长度无关,是以元素可所以长度分歧的数组。这样,Java的多维数组就不必然是规端方矩的矩阵了,可以千变万化。
, A6 B" M7 }, }1 Z
" m6 t9 ]' T, k) v 栈有一个很主要的非凡性,就是存在栈中的数据可以共享。假设我们同时界说:
4 g% `7 K2 Z; W- M$ n, e int a = 3;
7 E+ I: D& p) O) {- |+ T# n int b = 3;
- h8 ?2 L+ V5 @' v! g 编译器先措置int a = 3;首先它会在栈中建树一个变量为a的引用,然后查找栈中是否有3这个值,如不美观没找到,就将3存放进来,然后将a指向3。接着措置int b = 3;在建树完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就呈现了a与b同时均指向3的情形。这时,如不美观再令a=4;那么编译器 会年夜头搜索栈中是否有4值,如不美观没有,则将4存放进来,并令a指向4;如不美观已经有了,则直接将a指向这个地址。是以a值的改变不会影响到b的值。要注重这 种数据的共享与两个对象的引用同时指向一个对象的这种共享是分歧的,因为这种情形a的改削并不会影响到b, 它是由编译器完成的,它有利于节约空间。而一个对象引用变量改削了这个对象的内部状况,会影响到另一个对象引用变量。) e0 C' e3 D& ?
ps:关于c++的内存分配
3 \; m# E! l# V. Q( t8 E/ U& B: ~ 一个由C/C++编译的轨范占用的内存分为以下几个部门: k! G: m5 A1 U" \
1、栈区(stack)— 由编译器自动分潘晔着 ,存放函数的参数值,局部变量的值等。其操作体例近似于数据结构中的栈。 C6 A/ f0 ~% q1 `, e$ i2 x
2、堆区(heap) — 一般由轨范员分潘晔着, 若轨范员不释放,轨范竣事时可能由OS收受接管 。注重它与数据结构中的堆是两回事,分配体例却是近似于链表,呵呵。
g) K! R0 a7 f 3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 – 轨范竣事后有系统释放
5 a$ [0 J0 q4 b4 H8 W7 Z6 c 4、文字常量区 —常量字符串就是放在这里的。 轨范竣事后由系统释放
- n" S4 m; g% e' a% y, m 5、轨范代码区—存放函数体的二进制代码。
. p" s$ G+ @7 v) J$ j: a8 m 二、例子轨范1 F% f1 F" n0 Y1 ^6 `
这是一个前辈写的,很是具体
Q; G! C: X; Q0 e7 t //main.cpp, J% V1 X& e/ w2 G! M( a; s
int a = 0; 全局初始化区# r7 }' Q5 }$ @. p0 z1 ^: d
char *p1; 全局未初始化区
' v# p" r. d" L# d" C main()
1 }7 q' B j9 X5 K; X, s9 ^ {) @& x. _ o4 f0 d: C% ~1 n
int b; 栈) U8 \# j- A8 z+ t* M
char s[] = “abc”; 栈
i/ c6 Q+ L4 I2 L# ? char *p2; 栈/ z5 A" ?! p I
char *p3 = “123456″; 123456在常量区,p3在栈上。
! e4 J& z, m- P% c9 l0 P- @ static int c =0; 全局(静态)初始化区* v, u2 ~$ F: E/ @: {+ L9 X f
p1 = (char *)malloc(10);
+ Q$ y% c; I5 T3 O4 H p2 = (char *)malloc(20);: x# V9 @( a2 ~3 a# z8 T
分配得来得10和20字节的区域就在堆区。
' Y3 K7 W& |! U" p, ? strcpy(p1, “123456″); 123456放在常量区,编译器可能会将它与p3所指向的”123456″优化成一个处所。
/ a5 B# c* Y1 k0 C% @% r! U8 L. V } |