字符串
在class文件中,字符串是以MUTF8格式保存的,这一点之前了解过了。在Java虚拟机运行期间,字符串以java.lang.String对象的形式存在,而在String对象内部,字符串又是以UTF16格式保存。字符串相关功能大部分都是由String(和StringBuilder类等)类提供的,本节我们只学习一些辅助功能即可。
String类有两个实例变量,其中一个是value,类型是字符数组,用于存放UTF16编码后的字符序列。另一个是hash,缓存字符串的哈希码。有兴趣可以再看下Java中String类的实现。
字符串池
在Java中,String类提供了intern()实例方法,可以把自己放入字符串池,这里我们也来实现这样一个字符串池的功能。实现代码位于/rtdata/heap/string_pool.go文件中。在此之前,需要添加反射功能:
修改/rtdata/heap/class.go文件,添加通过名称和描述符获取成员变量的方法:
1234567891011121314// 通过名称和描述符获取成员变量func (self *Class) getField(name, descriptor string, isStatic bool) *Field {for c := self; c != nil; c = c.superClass {for _, field := range c.fields {if field.IsStatic() == isStatic &&field.name == name &&field.descriptor == descriptor {return field}}}return nil}修改/rtdata/heap/object.go文件,添加反射支持:
123456789101112// 通过反射获取属性的值func (self *Object) GetRefVar(name, descriptor string) *Object {field := self.class.getField(name, descriptor, false)slots := self.data.(Slots)return slots.GetRef(field.slotId)}// 通过反射设置属性的值func (self *Object) SetRefVar(name, descriptor string, ref *Object) {field := self.class.getField(name, descriptor, false)slots := self.data.(Slots)slots.SetRef(field.slotId, ref)}
下面就来实现字符串池功能:
完善ldc指令
修改/instructions/constants/ldc.go文件的_ldc()方法,主要的修改如下:
完善类加载器
修改/rtdata/heap/class_loader.go文件的initStaticFinalVar()方法,只要添加String的case就可以了:
测试字符串
修改main.go文件的startJVM()函数,仅需要修改一行:
1instructions.Interpret(mainMethod, cmd.VerboseInstFlag, cmd.Args)修改interpreter.go文件的Interpret()方法,添加args参数:
12345678910111213141516171819202122func Interpret(method *heap.Method, logInst bool, args []string) {...// 将参数转成Object引用jArgs := createArgsArray(method.Class().Loader(), args)// 将引用存储到局部变量表第0位frame.LocalVars().SetRef(0, jArgs)...}// 生成java字符串数组func createArgsArray(loader *heap.ClassLoader, args []string) *heap.Object {// 加载java.lang.String类stringClass := loader.LoadClass("java/lang/String")// 创建数组argsArr := stringClass.ArrayClass().NewArray(uint(len(args)))jArgs := argsArr.Refs()for i, arg := range args {// 获取Java StringjArgs[i] = heap.JString(loader, arg)}return argsArr}修改/instructions/references/invokevirtual.go文件的_print()函数,支持打印字符串:
1234case "(Ljava/lang/String;)V":jStr := stack.PopRef()goStr := heap.GoString(jStr)fmt.Println(goStr)修改/rtdata/heap/string_pool.go文件,添加Java字符串转Go字符串的方法:
1234567891011// java.lang.String -> go stringfunc GoString(jStr *Object) string {charArr := jStr.GetRefVar("value", "[C")return utf16ToString(charArr.Chars())}// utf16 -> utf8func utf16ToString(s []uint16) string {runes := utf16.Decode(s) // func Decode(s []uint16) []runereturn string(runes)}测试HelloWorld,新建HelloWorld.java,代码参照项目源码。
12345cd /home/zhangjing/IdeaProjects/jvmgo/gogo install cn.didadu/jvmgo./bin/jvmgo -classpath /home/zhangjing/IdeaProjects/jvmgo/java/target/classes cn.didadu.HelloWorld// 输出如下Hello, world!再测试个带参数的例子,新建PrintArgs.java,代码也比较简单,参照项目源码。
123456cd /home/zhangjing/IdeaProjects/jvmgo/gogo install cn.didadu/jvmgo./bin/jvmgo -classpath /home/zhangjing/IdeaProjects/jvmgo/java/target/classes cn.didadu.PrintArgs Hello World!// 输出如下HelloWorld!
数组和字符串的学习就到此结束了。