轻松打开Excel文件的多种实用方法与技巧探索
621
2022-05-29
首先编写一测试程序
public class Test {
public static void main(String[] args){
System.out.println("HelloWorld");
}
}
执行javac Test.java 得到Test.class文件(编译过程有点复杂,这里先不看)
执行java Test,控制台输出"test",想要弄清楚java程序是怎么运行起来首先得了解清楚class文件
看下Test.class里究竟是什么东西,class文件的内容如下:
上图中都是以16进制表示,接下来挨个分析其中的内容表示什么意思。class文件中存储的数据可以参考下表:
1、magic 魔数
CA FE BA BE
魔数,确定该文件是否是虚拟机可以接受的文件
2、class文件版本信息
00 00 00 33
class文件的版本号,51表示jdk1.7.0
3、常量池
3.1常量池入口
00 1D
常量池数量为29-1=28,每个类只有一个常量池
常量池中放了字符串,常量值,类名称,字段名,方法名等,反编译下Test.class,看看常量池中存放了哪些东西
Constant pool:
#1 = Methodref #6.#15 // java/lang/Object."
#2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #18 // test
#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #21 // Test
#6 = Class #22 // java/lang/Object
#7 = Utf8
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 SourceFile
#14 = Utf8 Test.java
#15 = NameAndType #7:#8 // "
#16 = Class #23 // java/lang/System
#17 = NameAndType #24:#25 // out:Ljava/io/PrintStream;
#18 = Utf8 test
#19 = Class #26 // java/io/PrintStream
#20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
#21 = Utf8 Test
#22 = Utf8 java/lang/Object
#23 = Utf8 java/lang/System
#24 = Utf8 out
#25 = Utf8 Ljava/io/PrintStream;
#26 = Utf8 java/io/PrintStream
#27 = Utf8 println
#28 = Utf8 (Ljava/lang/String;)V
常量池中的项目类型有:
CONSTANT_Utf8_info tag标志位为1, UTF-8编码的字符串,比如类或接口的全限定名,参数名等
CONSTANT_Integer_info tag标志位为3, int整型字面量
CONSTANT_Float_info tag标志位为4, float浮点型字面量
CONSTANT_Long_info tag标志位为5, long长整形字面量
CONSTANT_Double_info tag标志位为6, double双精度字面量
CONSTANT_Class_info tag标志位为7, 类或接口的符号引用,指向包含字符串字面值的CONSTANT_Utf8表
CONSTANT_String_info tag标志位为8,字符串类型的字面量,指向包含字符串字面值的CONSTANT_Utf8表
CONSTANT_Fieldref_info tag标志位为9, 字段的符号引用,指向包含该字段所属类名的CONSTANT_Utf8表
CONSTANT_Methodref_info tag标志位为10,类中方法的符号引用,指向包含该方法所属类型的CONSTANT_Utf8表
CONSTANT_InterfaceMethodref_info tag标志位为11, 接口中方法的符号引用
CONSTANT_NameAndType_info tag 标志位为12,字段和方法的名称以及类型的符号引用
3.2常量池内容
接上,继续分析class中的内容,参照 jvm官方文档 ,看下常量池中究竟是什么东西
常量池1-----0A 00 06 00 0F //
1,0A---tag为10,表示第一个常量类型为CONSTANT_Methodref,参照官方文档,CONSTANT_Methodref的结构为
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
所以后面跟了4个字节
2,00 06---声明当前方法类描述符索引值为6 // java/lang/Object
3,00 0F---当前方法的名称和类型索引值为15 // "
所以,结合上文中反编译出的内容来看,这几个16进制翻译过来正好是
#1 = Methodref #6.#15 // java/lang/Object."
常量池2----09 00 10 00 11
1,09---tag为9,类型为CONSTANT_Fieldref
2,00 10---声明当前方法类描述符索引值为16 // java/lang/System
3,00 11---字段描述符的名称和类型索引值为17 // out:Ljava/io/PrintStream;
这几个16进制翻译过来正好是
#2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream;
常量池3---08 00 12
1,08---tag为8,类型为CONSTANT_String,根据官方文档,其结构为
CONSTANT_String_info {
u1 tag;
u2 string_index;
}
所以后面跟了两个字节
2,00 12---声明当前String值所在的索引值为18
当前16进制翻译过来,表示
#3 = String #18 // test
常量池4---0A 00 13 00 14,对照着上面的分析,
#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
常量池5---07 00 15
1,07---tag为7,类型为CONSTANT_Class,根据官方文档,其结构为
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
2,00 15----当前类名称的索引值为21
#5 = Class #21 // Test
常量池6---07 00 16,对照上面的分析
#6 = Class #22 // java/lang/Object
常量池7---01 00 06 3C 69 6E 69 74 3E
1,01---tag为1,类型为CONSTANT_Utf8,根据官方文档
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
2,06---表示字符串的长度为6
3,3C 69 6E 69 74 3E ---字符串
#7 = Utf8
常量池8---01 00 03 28 29 56
常量池9---01 00 04 43 6F 64 65
常量池10---01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65
常量池11---01 00 04 6D 61 69 6E
常量池12---01 00 16 28 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56
常量池13---01 00 0A 53 6F 75 72 63 65 46 69 6C 65
常量池14---01 00 09 54 65 73 74 2E 6A 61 76 61
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 SourceFile
#14 = Utf8 Test.java
常量池15---0C 00 07 00 08
1,0C---tag为11,类型为CONSTANT_NameAndType,参照jvm官方文档,其结构为
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
}
2,00 07---该字段或方法名称常量索引值为7,即
#7 = Utf8
3,00 08---该字段或方法描述符常量索引值为8 ,即
#8 = Utf8 ()V
常量池16---07 00 17
常量池17---0C 00 18 00 19
常量池18---01 00 04 74 65 73 74
常量池19---07 00 1A
常量池20---0C 00 1B 00 1C
常量池21---01 00 04 54 65 73 74
常量池22---01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74
常量池23---01 00 10 6A 61 76 6A 2F 6C 61 6E 67 2F 53 79 73 74 65 6D
常量池24---01 00 03 6F 75 74
常量池25---01 00 15 4C 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B
常量池26---01 00 13 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D
常量池27---01 00 07 70 72 69 6E 74 6C 6E
常量池28---01 00 15 28 4 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56
#16 = Class #23 // java/lang/System
#17 = NameAndType #24:#25 // out:Ljava/io/PrintStream;
#18 = Utf8 test
#19 = Class #26 // java/io/PrintStream
#20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
#21 = Utf8 Test
#22 = Utf8 java/lang/Object
#23 = Utf8 java/lang/System
#24 = Utf8 out
#25 = Utf8 Ljava/io/PrintStream;
#26 = Utf8 java/io/PrintStream
#27 = Utf8 println
#28 = Utf8 (Ljava/lang/String;)V
到此常量池结束
4、访问标志access_flags
00 21----Test类的访问标志,参照官方文档,访问标志有
0x0021 = 0x0020|0x0001,即ACC_PUBLIC和ACC_SUPER为真,ACC_PUBLIC好理解,ACC_SUPER这是什么鬼,翻看官方文档,原文如下:
The ACC_SUPER flag indicates which of two alternative semantics is to be expressed by the invokespecial instruction (§invokespecial) if it appears in this class. Compilers to the instruction set of the Java Virtual Machine should set the ACC_SUPER flag.
The ACC_SUPER flag exists for backward compatibility with code compiled by older compilers for the Java programming language. In Oracle’s JDK prior to release 1.0.2, the compiler generated ClassFile access_flags in which the flag now representing ACC_SUPER had no assigned meaning, and Oracle's Java Virtual Machine implementation ignored the flag if it was set.
为了兼容之前的jdk版本,在jdk1.0.2之后这个编译出来的为真
5,类索引,父类索引,接口索引
接下来就是类索引,父类索引,接口索引
00 05------类索引值为#5
#5 = Class #21 // Test
00 06-----父类索引值为#6
#6 = Class #22 // java/lang/Object
00 00----类没有实现接口,接口数为0,所以后面没有接口信息
6、字段
00 00----当前类有0个字段
7、方法,指令
00 02----当前类有两个方法,参照官方文档,方法的结构如下:
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
方法1:00 01 00 07 00 08 00 01
----00 01:access_flags=0x0001=ACC_PUBLIC,方法的访问标志如下表:
---00 07:name_index=#7----->#7 = Utf8
---00 08:descriptor_index=#8------>#8 = Utf8 ()V
---00 01:attributes_count=1,所以紧随其后就是attribute_info部分,根据官方文档,其结构如下:
Code_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
00 09 00 00 00 1D 00 01 00 01 00 00 00 05 //非指令部分
---00 09:attribute_name_index=#9---------->#9 = Utf8 Code
---00 00 00 1D:attribute_length=29,所以整个属性表的长度为29+6=35,见官方文档说明:The value of the attribute_length item indicates the length of the attribute, excluding the initial six bytes.
---00 01:max_stack=1
---00 01:max_locals=1
---00 00 00 05:code_length=5
紧接着就是方法1的指令部分:
2A B7 00 01 B1
---2A:aload_0 ,
---B7 00 01 ->invokespecial #1,调用超类构造方法
---B1--->return
方法1的Exception:
00 00:方法没有throw异常
方法1的attribute count:
00 01://方法1最后有一个属性块,其结构如下:
LineNumberTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 line_number_table_length;
{ u2 start_pc;
u2 line_number;
} line_number_table[line_number_table_length];
}
00 0A 00 00 00 06 00 01
00 00 00 01
---00 0A:attribute_name_index=#10---->#10 = Utf8 LineNumberTable
---00 00 00 06:attribute_lenght=6
---00 01:line_number_table_length=1,表示这个LineNumberTable中有一条记录
---00 00 00 01:表示Test.java的第一行代码对应指令0--->0: aload_0
方法2:00 09 00 0B 00 0C 00 01
---00 09:access_flags=0x0008|0x0001=ACC_STATIC|ACC_PUBLIC
---00 0B:name_index=#11------>#11 = Utf8 main
---00 0C:descriptor_index=#12----->#12 = Utf8 ([Ljava/lang/String;)V
---00 01:arrtibutes_count=1,紧接着是attribute_info
方法2的code,非指令部分:
00 09 00 00 00 25 00 02 00 01 00 00 00 09
---00 09:attribute_name_index=#9----->#9 = Utf8 Code
---00 00 00 25:attribute_length=37,所以整个属性表的长度为43
---00 02:max_stack=2
---00 01:max_locals=1
---00 00 00 09:code_length=17
方法2的code,指令部分
B2 00 02----->getstatic #2:获取指定类的静态域,并且压入到栈顶,#2表示#2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream;
12 03--->ldc #3,将“test”常量值从常量池中压入到栈顶
B6 00 04---->invokervirtual #4,调用实例方法,#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V,即println方法
B1---->return
方法2的Exception:
00 00 ----->没有定义throw
方法2的attribute_count:
00 01 //方法最后有个属性
方法2的LineNumberTable:
00 0A 00 00 00 0A 00 02
----00 0A:attribute_name_index=#10------>#10 = Utf8 LineNumberTable
----00 00 00 0A:attribute_length=10
----00 02:line_number_table_lenght=2,表示lineNumberTable中有2条记录
00 00 00 04:Test.java第4行对应指令0 --->getstatic #2
00 08 00 05:Test.java第5行对应指令8----->8: return
8.sourceFile属性
00 01:当前class文件包含1个attribute_info
00 0D 00 00 00 02 00 0E
---00 0D:attribute_name_index=#13---->#13 = Utf8 SourceFile
---00 00 00 02:attribute_length=2
---00 0E:sourcefile_index=#14---->#14 = Utf8 Test.java
至此,class文件中的内容分析完毕!
Java
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。