Java字节码详解系列之二--解析字节码
2018-04-03 16:41
288 查看
1、javap查看字节码内容上文介绍了字节码的结构,本文主要通过一个简单的例子说明class字节码的每一个字段。package com.zcm.test;
import java.io.Serializable;
public class SourceTest implements Serializable{
public int a=3;
static Integer b=6;
String s="Hello World!";
public static void main(String[] args){
SourceTest test=new SourceTest();
test.a=9;
b=8;
}
private String test(){
return s;
}
}使用javac -d SourceTest.java 编译该文件,生成SourceTest.class。
使用javap -verbose SourceTest 就会分析出该class的字节码内容Classfile /Users/zcm/Downloads/com/zcm/test/SourceTest.class
Last modified 2018-4-2; size 694 bytes
MD5 checksum 5874dd9682f495b853d6ccb7b8af652b
Compiled from "SourceTest.java"
public class com.zcm.test.SourceTest implements java.io.Serializable
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #9.#28 // java/lang/Object."<init>":()V
#2 = Fieldref #5.#29 // com/zcm/test/SourceTest.a:I
#3 = String #30 // Hello World!
#4 = Fieldref #5.#31 // com/zcm/test/SourceTest.s:Ljava/lang/String;
#5 = Class #32 // com/zcm/test/SourceTest
#6 = Methodref #5.#28 // com/zcm/test/SourceTest."<init>":()V
#7 = Methodref #33.#34 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
#8 = Fieldref #5.#35 // com/zcm/test/SourceTest.b:Ljava/lang/Integer;
#9 = Class #36 // java/lang/Object
#10 = Class #37 // java/io/Serializable
#11 = Utf8 a
#12 = Utf8 I
#13 = Utf8 b
#14 = Utf8 Ljava/lang/Integer;
#15 = Utf8 s
#16 = Utf8 Ljava/lang/String;
#17 = Utf8 <init>
#18 = Utf8 ()V
#19 = Utf8 Code
#20 = Utf8 LineNumberTable
#21 = Utf8 main
#22 = Utf8 ([Ljava/lang/String;)V
#23 = Utf8 test
#24 = Utf8 ()Ljava/lang/String;
#25 = Utf8 <clinit>
#26 = Utf8 SourceFile
#27 = Utf8 SourceTest.java
#28 = NameAndType #17:#18 // "<init>":()V
#29 = NameAndType #11:#12 // a:I
#30 = Utf8 Hello World!
#31 = NameAndType #15:#16 // s:Ljava/lang/String;
#32 = Utf8 com/zcm/test/SourceTest
#33 = Class #38 // java/lang/Integer
#34 = NameAndType #39:#40 // valueOf:(I)Ljava/lang/Integer;
#35 = NameAndType #13:#14
4000
// b:Ljava/lang/Integer;
#36 = Utf8 java/lang/Object
#37 = Utf8 java/io/Serializable
#38 = Utf8 java/lang/Integer
#39 = Utf8 valueOf
#40 = Utf8 (I)Ljava/lang/Integer;
{
public int a;
descriptor: I
flags: ACC_PUBLIC
static java.lang.Integer b;
descriptor: Ljava/lang/Integer;
flags: ACC_STATIC
java.lang.String s;
descriptor: Ljava/lang/String;
flags:
public com.zcm.test.SourceTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_3
6: putfield #2 // Field a:I
9: aload_0
10: ldc #3 // String Hello World!
12: putfield #4 // Field s:Ljava/lang/String;
15: return
LineNumberTable:
line 5: 0
line 6: 4
line 8: 9
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #5 // class com/zcm/test/SourceTest
3: dup
4: invokespecial #6 // Method "<init>":()V
7: astore_1
8: aload_1
9: bipush 9
11: putfield #2 // Field a:I
14: bipush 8
16: invokestatic #7 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
19: putstatic #8 // Field b:Ljava/lang/Integer;
22: return
LineNumberTable:
line 11: 0
line 12: 8
line 13: 14
line 14: 22
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: bipush 6
2: invokestatic #7 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: putstatic #8 // Field b:Ljava/lang/Integer;
8: return
LineNumberTable:
line 7: 0
}
SourceFile: "SourceTest.java"
通过javap -verbose命令可以看到字节码所包含的信息。
2、查看字节码二进制在Mac系统下可以通过vim查看class文件,键入:%!xxd使用十六进制查看
3、class文件结构详解魔数所有的.class字节码文件的开始4个字节都是魔数,并且其值一定是0xCAFEBABE。如果该值不是0xCAFEBABE,则JVM拒绝加载该文件。
版本号魔数之后的4个字节为版本信息,前2个字节表示major version,即主版本号;后2个字节表示minor version,即次版本号。
这里的版本号值为0x00000034对应的十进制数是52,这里我用Java1.8编译出来的,其值是52。如果class文件的版本号超出了虚拟机支持的最高版本号,JVM将抛出java.lang.UnsupportedClassVersionError异常。
常量池Java类中的绝大多数信息都由常量池描述,常量池主要由常量池数量和常量池数组俩部分组成。常量池数量紧跟在次版本号的后面,占2个字节,class字节码的排列非常紧凑。而常量池数组紧跟在常量池数量之后。
每一个常量池元素都由两部分组成:tag和数组内容。使用结构化的方式来描述常量池数组的编排,描述如下:
tag1元素内容tag2元素内容tag3元素内容tag4元素内容....
常量池一共定义了11中常量,如下表:
JVM常量表
根据前面介绍的字节码格式,紧接着的第9,10字节是常量池的数量,后面是常量池数组内容。
下面给出JVM所定义的常量池中每一种元素的具体结构。
接着上面的分析,后面跟在常量池数量后面的就是常量池数组,按照上面JVM常量表的顺序排列。这里就不一一描述了。
访问标识 access_flags常量池后面紧跟的就是access_flags结构,该结构占2个字节,类型是u2。代表访问标志位,用于标注类或接口的访问信息。access_flags的可选值如下:
this_class该结构类型是u2,占2个字节。其值指向常量池对应的索引值。值是CONSTANT_Class_info类型,记录当前类的全限定名。
super_class该结构类型是u2,占2个字节。其值指向常量池对应的索引值。值是CONSTANT_Class_info类型,记录父类的全限定名。
interfacesinterfaces包含interfaces_count和interfaces[interfaces_count]。interfaces_count类型是u2,占2个字节。记录当前类所实现的接口数量。
interfaces[interfaces_count]是一组u2类型数据的集合。记录了当前类实现了哪些接口。如果没有实现过接口,就没有interface这一项。
field_infofields_count结构同上,记录了当前类所定义的变量的总数量。包括类成员变量和类变量(静态变量)。
fields[fields_count]这里记录类中所定义的各个变量的详细信息,包括变量名,变量类型,访问标识,属性等。
其中access_flags有如下的可选项。
method_infomethod_info包含methods_count和methods[methods_count];methods_count类型是u2,占2个字节。描述类中共包含多少个方法。methods_count后面是methods数组。该数组格式如下表:
methods数组各项结构与fields类似,这里不再赘述。
其中,access_flags的可选值如下:
Attribute属性表集合
在class文件中,属性表,方法表中都可以包含自己的属性表,用于描述某些场景的专有信息。为了能正确解析class文件,虚拟机规范中预定义了虚拟机实现必须能够识别的几种属性,除此以外,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java虚拟机运行时会忽略掉它不认识的属性。
限于篇幅,每种属性的结构就不介绍了。感兴趣的同学可以查看JVM官方文档。 到此,我们分析了一个具体的Java程序生成的字节码文件的格式和内容。了解class文件结构可以帮助我们更深入的理解JVM的运行原理。本文限于篇幅,很多细节的地方没有分析到位,但是JVM定义的内容大致都有介绍,详细的信息大家可以参考JVM官方文档。
import java.io.Serializable;
public class SourceTest implements Serializable{
public int a=3;
static Integer b=6;
String s="Hello World!";
public static void main(String[] args){
SourceTest test=new SourceTest();
test.a=9;
b=8;
}
private String test(){
return s;
}
}使用javac -d SourceTest.java 编译该文件,生成SourceTest.class。
使用javap -verbose SourceTest 就会分析出该class的字节码内容Classfile /Users/zcm/Downloads/com/zcm/test/SourceTest.class
Last modified 2018-4-2; size 694 bytes
MD5 checksum 5874dd9682f495b853d6ccb7b8af652b
Compiled from "SourceTest.java"
public class com.zcm.test.SourceTest implements java.io.Serializable
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #9.#28 // java/lang/Object."<init>":()V
#2 = Fieldref #5.#29 // com/zcm/test/SourceTest.a:I
#3 = String #30 // Hello World!
#4 = Fieldref #5.#31 // com/zcm/test/SourceTest.s:Ljava/lang/String;
#5 = Class #32 // com/zcm/test/SourceTest
#6 = Methodref #5.#28 // com/zcm/test/SourceTest."<init>":()V
#7 = Methodref #33.#34 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
#8 = Fieldref #5.#35 // com/zcm/test/SourceTest.b:Ljava/lang/Integer;
#9 = Class #36 // java/lang/Object
#10 = Class #37 // java/io/Serializable
#11 = Utf8 a
#12 = Utf8 I
#13 = Utf8 b
#14 = Utf8 Ljava/lang/Integer;
#15 = Utf8 s
#16 = Utf8 Ljava/lang/String;
#17 = Utf8 <init>
#18 = Utf8 ()V
#19 = Utf8 Code
#20 = Utf8 LineNumberTable
#21 = Utf8 main
#22 = Utf8 ([Ljava/lang/String;)V
#23 = Utf8 test
#24 = Utf8 ()Ljava/lang/String;
#25 = Utf8 <clinit>
#26 = Utf8 SourceFile
#27 = Utf8 SourceTest.java
#28 = NameAndType #17:#18 // "<init>":()V
#29 = NameAndType #11:#12 // a:I
#30 = Utf8 Hello World!
#31 = NameAndType #15:#16 // s:Ljava/lang/String;
#32 = Utf8 com/zcm/test/SourceTest
#33 = Class #38 // java/lang/Integer
#34 = NameAndType #39:#40 // valueOf:(I)Ljava/lang/Integer;
#35 = NameAndType #13:#14
4000
// b:Ljava/lang/Integer;
#36 = Utf8 java/lang/Object
#37 = Utf8 java/io/Serializable
#38 = Utf8 java/lang/Integer
#39 = Utf8 valueOf
#40 = Utf8 (I)Ljava/lang/Integer;
{
public int a;
descriptor: I
flags: ACC_PUBLIC
static java.lang.Integer b;
descriptor: Ljava/lang/Integer;
flags: ACC_STATIC
java.lang.String s;
descriptor: Ljava/lang/String;
flags:
public com.zcm.test.SourceTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_3
6: putfield #2 // Field a:I
9: aload_0
10: ldc #3 // String Hello World!
12: putfield #4 // Field s:Ljava/lang/String;
15: return
LineNumberTable:
line 5: 0
line 6: 4
line 8: 9
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #5 // class com/zcm/test/SourceTest
3: dup
4: invokespecial #6 // Method "<init>":()V
7: astore_1
8: aload_1
9: bipush 9
11: putfield #2 // Field a:I
14: bipush 8
16: invokestatic #7 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
19: putstatic #8 // Field b:Ljava/lang/Integer;
22: return
LineNumberTable:
line 11: 0
line 12: 8
line 13: 14
line 14: 22
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: bipush 6
2: invokestatic #7 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: putstatic #8 // Field b:Ljava/lang/Integer;
8: return
LineNumberTable:
line 7: 0
}
SourceFile: "SourceTest.java"
通过javap -verbose命令可以看到字节码所包含的信息。
2、查看字节码二进制在Mac系统下可以通过vim查看class文件,键入:%!xxd使用十六进制查看
3、class文件结构详解魔数所有的.class字节码文件的开始4个字节都是魔数,并且其值一定是0xCAFEBABE。如果该值不是0xCAFEBABE,则JVM拒绝加载该文件。
版本号魔数之后的4个字节为版本信息,前2个字节表示major version,即主版本号;后2个字节表示minor version,即次版本号。
这里的版本号值为0x00000034对应的十进制数是52,这里我用Java1.8编译出来的,其值是52。如果class文件的版本号超出了虚拟机支持的最高版本号,JVM将抛出java.lang.UnsupportedClassVersionError异常。
常量池Java类中的绝大多数信息都由常量池描述,常量池主要由常量池数量和常量池数组俩部分组成。常量池数量紧跟在次版本号的后面,占2个字节,class字节码的排列非常紧凑。而常量池数组紧跟在常量池数量之后。
每一个常量池元素都由两部分组成:tag和数组内容。使用结构化的方式来描述常量池数组的编排,描述如下:
tag1元素内容tag2元素内容tag3元素内容tag4元素内容....
常量池一共定义了11中常量,如下表:
编号 | 常量池元素名称 | tag位标识 | 含义 |
1 | CONSTANT_Utf8_info | 1 | UTF-8编码的字符串 |
2 | CONSTANT_Integer_info | 3 | 整型字面量 |
3 | CONSTANT_Float_info | 4 | 浮点型字面量 |
4 | CONSTANT_Long_info | 5 | 长整型字面量 |
5 | CONSTANT_Double_info | 6 | 双精度字面量 |
6 | CONSTANT_Class_info | 7 | 类或接口的符号引用 |
7 | CONSTANT_String_info | 8 | 字符串类型的字面量 |
8 | CONSTANT_Fieldref_info | 9 | 字段的符号引用 |
9 | CONSTANT_Methodref_info | 10 | 类中方法的符号引用 |
10 | CONSTANT_InterfaceMethodref_info | 11 | 接口中方法的符号引用 |
11 | CONSTANT_NameAndType_info | 12 | 字段和方法的名称以及类型的符号引用 |
下面给出JVM所定义的常量池中每一种元素的具体结构。
CONSTANT_Utf8_info | ||
类型 | 结构 | 说明 |
u1 | tag | CONSTANT_Utf8 (1) |
u2 | length | bytes所代表的字符串的长度 |
u1 | bytes[length] | 字符串的byte数据 |
CONSTANT_Integer_info | ||
类型 | 结构 | 说明 |
u1 | tag | CONSTANT_Integer (3) |
u4 | bytes | 整型常量值 |
CONSTANT_Float_info | ||
类型 | 结构 | 说明 |
u1 | tag | CONSTANT_Float(4) |
u4 | bytes | 单精度浮点型常量值 |
CONSTANT_Long_info | ||
类型 | 结构 | 说明 |
u1 | tag | CONSTANT_Long (5) |
u4 | high_bytes | 长整型的高四位值 |
u4 | low_bytes | 长整型的低四位值 |
CONSTANT_Double_info | ||
类型 | 结构 | 说明 |
u1 | tag | CONSTANT_Double(6) |
u4 | high_bytes | 双精度浮点的高四位值 |
u4 | low_bytes | 双精度浮点的低四位值 |
CONSTANT_Class_info format | ||
类型 | 结构 | 说明 |
u1 | tag | CONSTANT_Class (7) |
u2 | name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。表示类或接口名。 |
CONSTANT_String_info | ||
类型 | 结构 | 说明 |
u1 | tag | CONSTANT_String(8) |
u2 | string_index | constant_pool中的索引,CONSTANT_Utf8_info类型。表示String类型值。 |
CONSTANT_Fieldref_info | ||
类型 | 结构 | 说明 |
u1 | tag | CONSTANT_Fieldref(9) |
u2 | class_index | constant_pool中的索引,CONSTANT_Class_info类型。记录定义该字段的类或接口。 |
u2 | name_and_type_index | constant_pool中的索引,CONSTANT_NameAndType_info类型。指定类或接口中的字段名(name)和字段描述符(descriptor)。 |
CONSTANT_Methodref_info | ||
类型 | 结构 | 说明 |
u1 | tag | CONSTANT_Methodref(10) |
u2 | class_index | constant_pool中的索引,CONSTANT_Class_info类型。记录定义该方法的类。 |
u2 | name_and_type_index | constant_pool中的索引,CONSTANT_NameAndType_info类型。指定类中扽方法名(name)和方法描述符(descriptor)。 |
CONSTANT_InterfaceMethodref_info | ||
类型 | 结构 | 说明 |
u1 | tag | CONSTANT_InterfaceMethodref(11) |
u2 | class_index | constant_pool中的索引,CONSTANT_Class_info类型。记录定义该方法的接口。 |
u2 | name_and_type_index | constant_pool中的索引,CONSTANT_NameAndType_info类型。指定接口中的方法名(name)和方法描述符(descriptor)。 |
CONSTANT_NameAndType_info | ||
类型 | 结构 | 说明 |
u1 | tag | CONSTANT_NameAndType (12) |
u2 | name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定字段或方法的名称。 |
u2 | descriptor_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定字段或方法的描述符 |
访问标识 access_flags常量池后面紧跟的就是access_flags结构,该结构占2个字节,类型是u2。代表访问标志位,用于标注类或接口的访问信息。access_flags的可选值如下:
标志名称 | 值 | 含义 |
ACC_PUBLIC | 0×0001 | 是否为pubilc类型 |
ACC_FINAL | 0×0010 | 是否为final类型,只有类可设置 |
ACC_SUPER | 0×0020 | 用于兼容早期编译器,新编译器都设置该标记,以在使用invokespecial指令时对子类方法做特定处理。 |
ACC_INTERFACE | 0×0200 | 标识为接口。不可同时设置:ACC_FINAL、ACC_SUPER、ACC_ENUM |
ACC_ABSTRACT | 0×0400 | 抽象类,无法实例化。不可与ACC_FINAL同时设置。 |
ACC_SYNTHETIC | 0×1000 | synthetic,由编译器产生,不存在于源代码中。 |
ACC_ANNOTATION | 0×2000 | 注解类型(annotation),需同时设置:ACC_INTERFACE、ACC_ABSTRACT |
ACC_ENUM | 0×4000 | 枚举类型 |
super_class该结构类型是u2,占2个字节。其值指向常量池对应的索引值。值是CONSTANT_Class_info类型,记录父类的全限定名。
interfacesinterfaces包含interfaces_count和interfaces[interfaces_count]。interfaces_count类型是u2,占2个字节。记录当前类所实现的接口数量。
interfaces[interfaces_count]是一组u2类型数据的集合。记录了当前类实现了哪些接口。如果没有实现过接口,就没有interface这一项。
field_infofields_count结构同上,记录了当前类所定义的变量的总数量。包括类成员变量和类变量(静态变量)。
fields[fields_count]这里记录类中所定义的各个变量的详细信息,包括变量名,变量类型,访问标识,属性等。
field_info | ||
类型 | 名称 | 说明 |
u2 | access_flags | 记录字段的访问权限。 |
u2 | name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。表示变量的名称引用。 |
u2 | descriptor_index | constant_pool中的索引,CONSTANT_Utf8_info类型,表示变量的类型信息引用。 |
u2 | attributes_count | attributes包含的项目数。 |
attribute_info | attributes[attributes_count] | 字段中包含的Attribute集合。 |
字段的访问权限 | ||
标志名称 | 值 | 含义 |
ACC_PUBLIC | 0x0001 | pubilc,包外可访问。 |
ACC_PRIVATE | 0x0002 | private,只可在类内访问。 |
ACC_PROTECTED | 0x0004 | protected,类内和子类中可访问。 |
ACC_STATIC | 0x0008 | static,静态。 |
ACC_FINAL | 0x0010 | final,常量。 |
ACC_VOILATIE | 0x0040 | volatile,直接读写内存,不可被缓存。不可和ACC_FINAL一起使用。 |
ACC_TRANSIENT | 0x0080 | transient,在序列化中被忽略的字段。 |
ACC_SYNTHETIC | 0x1000 | synthetic,由编译器产生,不存在于源代码中。 |
ACC_ENUM | 0x4000 | enum,枚举类型字段 |
methods结构 | ||
类型 | 名称 | 说明 |
u2 | access_flags | 记录方法的访问权限。 |
u2 | name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定方法名称。 |
u2 | descriptor_index | constant_pool中的索引,CONSTANT_Utf8_info类型,指定方法的描述符。 |
u2 | attributes_count | attributes包含的项目数。 |
attribute_info | attributes[attributes_count] | 字段中包含的Attribute集合。 |
其中,access_flags的可选值如下:
方法的访问权限 | ||
标志名称 | 标志值 | 说明 |
ACC_PUBLIC | 0x0001 | pubilc,包外可访问。 |
ACC_PRIVATE | 0x0002 | private,只可在类内访问。 |
ACC_PROTECTED | 0x0004 | protected,类内和子类中可访问。 |
ACC_STATIC | 0x0008 | static,静态。 |
ACC_FINAL | 0x0010 | final,不可被重写。 |
ACC_SYNCHRONIZED | 0x0020 | synchronized,同步方法。 |
ACC_BRIDGE | 0x0040 | bridge方法,由编译器生成。 |
ACC_VARARGS | 0x0080 | 包含不定参数个数的方法。 |
ACC_NATIVE | 0x0100 | native,非Java语言实现的方法。 |
ACC_ABSTRACT | 0x0400 | abstract,抽象方法。 |
ACC_STRICT | 0x0800 | strictfp,设置floating-point模式为FP-strict。 |
ACC_SYNTHETIC | 0x1000 | synthetic,由编译器产生,不存在于源代码中。 |
在class文件中,属性表,方法表中都可以包含自己的属性表,用于描述某些场景的专有信息。为了能正确解析class文件,虚拟机规范中预定义了虚拟机实现必须能够识别的几种属性,除此以外,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java虚拟机运行时会忽略掉它不认识的属性。
属性名称 | 使用位置 | 含义 |
Code | 方法表 | Java代码编译成的字节码指令 |
ConstantValue | 字段表 | final关键字定义的常量值 |
Deprecated | 类文件,字段表,方法表 | 被声明为deprecated的方法和字段 |
Exceptions | 方法表 | 方法抛出的异常 |
InnerClasses | 类文件 | 内部类列表 |
LineNumberTable | Code属性 | Java源码的行号与字节码指令的对应关系 |
LocalVariableTable | Code属性 | 方法的局部变量描述 |
SourceFile | 类文件 | 源文件名称 |
Synthetic | 类文件,字段表,方法表 | 标识方法或字段是由编译器自动生成的 |
相关文章推荐
- 《视频直播技术详解》系列之二:采集
- 【dubbo源码解读系列】之二 dubbo代码启动入口解析(自定义main方法)
- [jvm解析系列][八]方法表集合,Code属性和Exceptions属性,你的字节码存在哪里了?
- 前端结构解析系列之二:凯旋门结构
- 用ant的build.xml构建自动化打包android apk 完全详解(打包系列教程之二)
- 告诉你们XML解析方法详解之自动化测试系列笔记
- RxJava系列之二 变换类操作符详解1
- Java基础系列17:使用DOM、SAX、JDOM、DOM4J解析XML文件详解
- LCS2005客户端配置详解:LCS2005系列之二
- STL工具库使用解析系列之二:自定义比较函数的两种方式(重载和仿函数)
- 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新
- 【vue系列之二】详解vue-cli 2.0配置文件
- DNS扫盲系列之二:域名解析及DNS功能分类
- Spark源码系列(九)Spark SQL初体验之解析过程详解
- 消息队列基础 RabbitMQ与AMQP协议详解——超大规模高可用OpenStack核心技术深入解析系列(二)
- [jvm解析系列][十三]字节码指令小节,从字节码看JVM的栈解释器执行过程。
- Java字节码深入解析 || 使用Intellij idea如何快速查看Java类字节码
- 死磕Spring系列之二,bean标签的解析和BeanDefinition的注册
- WatiN系列之二 解析Watin
- clayui界面库系列教程之二:详解CLAYUI_FRAME