在大部分编程语言中,数组和字符串都是最基本的数据类型。Java虚拟机直接支持数组,对字符串的支持则由java.lang.String和相关的类提供。本章就来学习数组和字符串相关指令。
数组概述
数组在Java虚拟机中是个比较特殊的概念。为什么这么说呢,有下面几个原因:
- 数组类和普通的类是不同的。普通的类从class文件中加载,但是数组类由Java虚拟机在运行时生成。数组的类名是左方括号([)+数组元素的类型描述符;数组类型描述符就是类名本身。例如int[]的类名是[I,int[][]的类名是[[I,Object[]的类名是[Ljava/lang/Object;,String[][]的类名是[[Ljava/lang/String;,等等。
- 创建数组的方式和创建普通对象的方式不同。普通对象由new指令创建,然后由构造函数初始化。基本类型数组由newarray指令创建;引用类型数组由anewarray指令创建;另外还有一个专门的multianewarray指令用于创建多维数组。
- 数组和普通对象存放的数据叶不同。普通对象中存放的是实例变量,通过putfield和各条field指令存取。数组对象中存放的则是数组元素,通过<t>aload和<t>astore系列指令按索引存取。其中<t>可以是a、b、c、d、f、i、l或者s。分别用于存取引用、byte、char、double、float、int、long或short类型的数组。
数组实现
将在类和对象的基础上实现数组类和数组对象。
数组对象
和普通对象一样,数组也是分配在堆中,通过引用来使用。所以需要改造Object结构体,让它既可以表示普通对象,也可以表示数组。修改/rtdata/heap/object.go文件:
需要给Object结构体添加几个数组特有的方法,单独定义在/rtdata/heap/array_object.go文件:
数组类
不需要修改Class结构体,只要给它添加几个数组特有的方法即可。为了强调这些方法只是针对数组类,把这些方法定义到新的文件/rtdata/heap/array_class.go中:
有一点要说明下,上面都没看到boolean类型的身影,因为使用的是[]int8来表示布尔数组。
加载数组类
修改/rtdata/heap/class_loader.go文件:
数组相关指令
下面将要实现20条指令,其中newarray、anewarray、multianewarray和arraylength指令属于引用类指令;<t>aload和<t>astore系列各有8条,分别属于加载类和存储类指令。下面在java项目中新建ArrayDamo.java,后面就拿该类做测试。
newarray指令
newarray指令用来创建基本类型数组,包括boolean[]、byte[]、char[]、short[]、int[]、long[]、float[]、double[]8种。其实现代码位于/instructions/references/newarray.go文件中:
newarray指令需要两个操作数。第一个操作数时一个uint8整数,在字节码中紧跟在指令操作码后面,表示要创建哪种类型的数组。Java虚拟机规范把这个操作数叫座atype,并且规定了它的有效值,在newarray.go文件中定义常量和读取第一个操作数的方法:
newarray指令的第二个操作数是count,从操作数栈中弹出,表示数组长度。在newarray.go文件中定义Execute()方法,在实现该方法之前要先修改下/rtdata/heap/class.go文件,添加获取当前class的ClassLoader,很简单,就一个普通的Getter方法,这里就不贴出来了。
anewarray指令
anewarray指令用来创建引用类型数组,其实现代码位于/instructions/references/anewarray.go文件中。anewarray指令也需要两个操作数。第一个操作数时uint16索引,来自字节码。通过这个索引可以从当前类的运行时常量池中找到一个类符号索引,解析这个符号引用就可以得到数组元素的类。第二个操作数是数组的长度,从操作数栈中弹出。在实现该指令之前,也要做些准备工作,下面先来看下:
将类型名转成类型描述符,新建/rtdata/heap/class_name_helper.go文件:
12345678910111213141516171819202122232425262728293031323334353637383940414243/*将类名转换成类型描述符XXX -> [LXXX;int -> [I[XXX -> [[XXX*/func getArrayClassName(className string) string {return "[" + toDescriptor(className)}/*将类名转成类型描述符XXX => LXXX;int => I[XXX => [XXX*/func toDescriptor(className string) string {// 如果已经是数组类名,直接返回if className[0] == '[' {// arrayreturn className}// 如果是基本类型名,返回对应的类型描述符if d, ok := primitiveTypes[className]; ok {// primitivereturn d}// 普通类名转成类型描述符L***;return "L" + className + ";"}// 基本类型和对应类型描述符mapvar primitiveTypes = map[string]string{"void": "V","boolean": "Z","byte": "B","short": "S","int": "I","long": "J","char": "C","float": "F","double": "D",}修改/rtdata/heap/class.go文件,添加加载数组类的方法:
123456func (self *Class) ArrayClass() *Class {// 获取数组类名arrayClassName := getArrayClassName(self.name)// 通过数组类名加载该数组类return self.loader.LoadClass(arrayClassName)}
准备工作做好,我们来实现anewarray指令:
arraylength指令
arraylength指令用于获取数组长度,实现代码位于/instructions/references/arraylength.go文件中,该指令只需要一个操作数,即从操作数栈顶弹出的数组引用。