深入理解Android:Java虚拟机ART
上QQ阅读APP看书,第一时间看更新

2.2 常量池及相关内容

2.2.1 常量项的类型和关系

Java虚拟机规范中,常量池的英文叫Constant Pool,对应的数据结构伪代码就是一个类型为cp_info的数组。每一个cp_info对象存储了一个常量项。cp_info对应数据结构的伪代码如下所示。

[cp_info伪代码]

cp_info {//u1表示该域对应一个字节长度,u表示unsigned
    u1 tag;//每一个cp_info的第一个字节表明该常量项的类型
    u1 info[];//常量项的具体内容
}

由伪代码可知,每一个常量项的第一个字节用于表明常量项的类型,紧接其后的才是具体的常量项内容了。那么,常量项会有哪些类型呢?

表2-1 常量项的类型和tag取值

表2-1列出了规范中所定义的Class文件常量项类型以及对应的tag取值。此处有两个常量项特别容易混淆。

CONSTANT_String和CONSTANT_Utf8的区别

CONSTANT_Utf8:该常量项真正存储了字符串的内容。以后我们将看到此类型常量项对应的数据结构中有一个字节数组,字符串就存储在这个字节数组中。

CONSTANT_String:代表了一个字符串,但是它本身不包含字符串的内容,而仅仅包含一个指向类型为CONSTANT_Utf8常量项的索引。

接下来我们来看看几种常见常量项的内容,如图2-2和图2-3所示。

图2-2 常量项Utf8、Class等对应的数据结构

图2-3 常量项Long、Integer等对应的数据结构

图2-2和图2-3展示了几种常见的常量项对应的数据结构。

我们先看图2-2。左上角的CONSTANT_Utf8_info,其length表示bytes数组的长度,而bytes成员则真正存储了字符串的内容。了解这一点信息很重要,因为图2-2中其他凡是需要表示字符串的地方实际上都是指向常量池中一个类型为CONSTANT_Utf8_info元素的索引。比如:

·CONSTANT_String_info(代表一个字符串)的string_index,它是一个索引,指向常量池中一个元素类型为Utf8_info(为行文简单,略去前面的CONSTANT_前缀)的项。所以,该字符串的真正内容通过string_index索引到一个Utf8_info元素即可获取。

·和String_info一样,Class_info里的name_index、MethodType_info里的descriptor_index、NameAndType_info里的name_index和descriptor_index都代表一个指向类型为Utf8_info元素的索引。

·类似这种间接引用关系在图2-2中Fieldref_info、Methodref_info、InterfaceMethodref_info里也有体现,只不过它们的class_index指向代表Class_info元素的索引,name_and_type_index指向代表NameAndType_info元素的索引。

提示 仔细揣摩,读者会发现图2-2中这个几个info最终包含的内容都是字符串。这也是它们之间能通过info来互相引用以减少空间占用的原因。

而图2-3的情况和图2-2完全不同。图2-3中的Double_info、Float_info、Integer_info和Long_info结构体内直接就能存储数据,这几个info之间没有引用关系。

对图2-2里的info而言,为什么不在每个常量项里直接包含字符串信息。而是采用这种间接引用元素索引的方式呢?原因很简单,就是为了节省Class文件的空间 Android的dex文件在Class文件基础上做了进一步的优化以节省空间,我们到后续章节再介绍dex文件格式。。来看一个简单的例子。

[Sample.java]

public class Sample{
    public String  m1; //声明两个String类型的成员变量m1和m2
    public String  m2;
}

上面这个Sample.java对应的Sample.class文件将包含两个CONSTANT_Fieldref_info常量项。如果每个常量项都直接包含字符串内容的话,会出现什么结果呢?

·class_index不再是索引,而应该存储字符串"Sample"。

·name_and_type_index也不再是索引,而将是m1对应的内容,它将包含"m1"和"Ljava/lang/String;"字符串,m2对应的内容将包含"m2"和"Ljava/lang/String;"字符串。其中,"m1"和"m2"是成员变量的名字,Ljava/lang/String是成员变量数据类型的字符串表示。

显然,上面的做法将出现冗余信息,比如"Ljava/lang/String;"和"Sample"字符串就各多了一份。而如果采用间接引用元素的方式就能节省空间了,如图2-4所示。

图2-4 利用索引来减少空间

图2-4中:

·m1和m2为Fieldref_info,它们的class_index指向左边Utf8_info里的"Sample",而name_and_type_index指向对应的NameAndType_info常量元素。

·NameAndType_info本身的name_index和descriptor_index又指向Utf8_info常量元素。所以,图中代表数据类型的"Ljava/lang/String;"可以复用一份内容,从而减少了空间占用。

除了采用引用索引的方式以节省空间外,规范对用于描述成员变量成员函数相关的字符串的格式也有要求。