上一章节已经实现了读取class文件,测试的时候还打印出一堆杂乱无章的数字,本章就来学习下,这些数字到底是什么,如何解析这些class文件。
class文件
作为类信息的载体,每个class文件都完整地定义了一个类,也就是说所有的信息都包含在那一堆数字当中,我们可以通过网络下载、从数据库加载,甚至是在运行中直接生成class文件。所以,class文件并非特指位于磁盘中的.class文件,而是泛指任何格式符合规范的class数据。
为了更好的理解class文件,我们再建一个java项目,和go项目同级,该项目的作用就是在需要的时候测试自己写的JVM。现在,新建一个class:
|
|
上述代码很简单,是为了学习编译后的class文件。《自己动手写Java虚拟机》的作者写了classpy的图形化工具,可以方便地查看反编译后的class文件。具体如何使用请参照作者的Github站点。编译ClassFile,用classpy打开ClassFile.class:
各种看不懂啊,没有关系,慢慢来。构成class文件的基本数据单位是以16进制表示的一个字节,默认按大端方式存储,可以把整个class文件当成一个字节流来处理。
Java虚拟机规范使用一种类似C语言的结构体来描述class文件,这种伪结构只有两种数据类型:无符号数和表。其中无符号数是基本数据类型以u1、u2和u4来分别代表1、2和4个字节无符号数。表是由多个无符号数或者其他表作为数据项的复合数据类型,所有的表都习惯以_info结尾。下表列出calss文件的构成:
类型 | 名称 | 数量 |
---|---|---|
u4 | magic | 1 |
u2 | minor_version | 1 |
u2 | major_version | 1 |
u2 | constant_pool_count | 1 |
cp_info | constant_pool | constant_pool_count - 1 |
u2 | access_flags | 1 |
u2 | this_class | 1 |
u2 | super_class | 1 |
u2 | interfaces_count | 1 |
u2 | interfaces | interfaces_count |
u2 | fields_count | 1 |
field_info | fields | fields_count |
u2 | methods_count | 1 |
method_info | methods | methods_count |
u2 | attribute_count | 1 |
attribute_info | attributes | attribute_count |
现在再看看那一串数字,看似杂乱无章,其实是由严格地规定组合而成,我们要做的就是从数字中把规定找出来,那么就可以揭开class文件的面纱了。
读取class文件
下面将一边学习class文件格式,一边编写代码实现class文件解析。Go语言内置了丰富的数据类型,非常适合处理class文件。下表列举了Go和Java基本数据类型对照关系:
Go语言类型 | Java语言类型 | 说明 |
---|---|---|
int8 | byte | 8比特有符号整数 |
unit8(别名byte) | N/A | 8比特无符号整数 |
int16 | short | 16比特有符号整数 |
uint16 | char | 16比特无符号整数 |
int32(别名rune) | int | 32比特有符号整数 |
uint32 | N/A | 32比特无符号整数 |
int64 | long | 64比特有符号整数 |
uint64 | N/A | 64比特无符号整数 |
float32 | float | 32比特IEEE-745浮点数 |
float64 | double | 64比特IEEE-745浮点数 |
先不探究class文件的规律,把它读出来,之前说过了可以把class文件当成字节流来处理,我们定义一个结构来存储数据。class_reader.go用于读取字节流
下面定义和Class文件结构数据类型一致的class_file.go
以上只是class_file.go的部分代码,后面一点点完善。其中的ConstantPool、MemberInfo和AttributeInfo都定义在其他文件中,暂时先不管,可以把文件建起来,数据结构定义好,能够编译通过就可以了。
这一章节其实也就做了一些解析class的准备工作,下面几个章节将对照class文件,详细学习下ClassFile中每一项的含义,同时完善代码。