上一篇Java Class文件格式详解 讲解了整个Class文件的组成部分,因为篇幅的原因没做太多过细的分析,今天我们重点分析下常量池。
一、示例代码介绍
为了加深认识,这篇文章会以一个实际的例子来讲解常量池中具体结构,代码如下:
package com.stock.test;
@RunWith(SpringJUnit4ClassRunner.class)
public class StringTest extends SpringTest {
@Test
public void testEncoding(){
writeToDb("中国");
return;
}
/**
* 写入到数据库
* @param str
*/
private void writeToDb(String str){
try {
String connStr;
connStr = "jdbc:mysql://127.0.0.1:3306/stock?useUnicode=true&characterEncoding=UTF-8";
Connection conn = DriverManager.getConnection(connStr, "root", "");
conn.setAutoCommit(false);
PreparedStatement pst = conn.prepareStatement("");
String sql = "INSERT INTO test(str) VALUES ('" + str + " ')";
pst.addBatch(sql);
pst.executeBatch();
conn.commit();
pst.close();
conn.close();
}catch (Exception ex){
}
}
/**
* 将内容保存到文件之中
* @param str
* @param path
*/
private void saveToFile(String str, String path){
try {
File file = new File(path);
if (!file.exists()) {
file.createNewFile();
}
//保存文件
FileOutputStream fileOutputStream = new FileOutputStream(file);
byte[] bytes = str.getBytes();
fileOutputStream.write(bytes);
fileOutputStream.flush();
fileOutputStream.close();
}catch (Exception ex){
ex.printStackTrace();
}
}
}
二、常量池中常量类型
上一篇文章讲了常量池总共17(上一篇写的是18,修正一下,因为2没有使用)种类型,我们来分析其中最常用的类型。
1、CONSTANT_Utf8
这种类型存放具体的字符串,这个是基础又基础的类型,好多其它类型都会间隔指向它。
具体格式:
名称 | 长度(单位为字节) | 说明 |
---|---|---|
tag | 1 | 类型,固定为1 |
length | 2 | 接下来的字符串的长度 |
bytes | length | 存放具体的字符串 |
如要存放saveToFile这个UTF8字符串,则大概的组成是这样的:
1000AsaveToFile
第1个字节是1,接下来2个字节是000A,表示长度为10,最后是存放具体的字符串内容
2、CONSTANT_Integer
名称 | 长度(单位为字节) | 说明 |
---|---|---|
tag | 1 | 类型,固定为3 |
bytes | 4 | 存放整型变量具体的数值 |
3、CONSTANT_Float、CONSTANT_Long、CONSTANT_Double
这几个和上面CONSTANT_Integer,区别在于存放具体的内容不一样,其中Double的数值是用8字节,其余是4字节
4、CONSTANT_Class
名称 | 长度(单位为字节) | 说明 |
---|---|---|
tag | 1 | 类型,固定为7 |
index | 2 | 指向类名常量项的索引,即指向CONSTANT_Utf8类型的索引 |
5、CONSTANT_String
一些字符常量就是通过这个类型表示的,像上面的writeToDb的参数:中国
名称 | 长度(单位为字节) | 说明 |
---|---|---|
tag | 1 | 类型,固定为8 |
index | 2 | 指向字符串值的索引,即指向CONSTANT_Utf8类型的索引 |
6、CONSTANT_Fieldref
描述类的成员,或者说字段
名称 | 长度(单位为字节) | 说明 |
---|---|---|
tag | 1 | 类型,固定为9 |
class_index | 2 | 指向类名的索引,即指向CONSTANT_Utf8类型的索引 |
desc_index | 2 | 指向成员描述符的索引项,即指向后面要讲的类型为CONSTANT_NameAndType的常量 |
7、CONSTANT_Methodref
描述方法
名称 | 长度(单位为字节) | 说明 |
---|---|---|
tag | 1 | 类型,固定为10 |
class_index | 2 | 指向类名的索引,即指向CONSTANT_Utf8类型的索引 |
desc_index | 2 | 指向方法描述符的索引项,如入参和返回值等信息 |
8、CONSTANT_InterfaceMethodref
同上面CONSTANT_Methodref,只不过指向接口的方法
9、CONSTANT_NameAndType
名称 | 长度(单位为字节) | 说明 |
---|---|---|
tag | 1 | 类型,固定为12 |
name_index | 2 | 指向方法名或字段名常量的索引 |
desc_index | 2 | 指向描述符常量索引索引 |
其它几种类型用的比较少,就不在这里一一介绍了。
三、实例分析
接下来我们分析上面代码中字节码
上面是整个字节码的部分截图,我们来逐一分析下。
前面4字节是CA FE BA BE,这是固定的魔幻数;
接下来4字节是版本号:00 00 00 34 ,换成10进制是52,这是JDK8的版本号;
接下来是00 8C ,这个表示常量池常量项的个数,一共有140-1=139项。
接下来是具体的常量项了,看第1个字节,这个表示类型,这里值为0A,即10,对应的类型是CONSTANT_Methodref,按照上面的分析这种格式接下来是2字节的类名索引和2字节的类型索引,接下来2字节是0022,再接下来2字节是004C,表示类名索引是34,描述索引是76,那整个常量池的第34和76项是什么呢,可以用Javap看下结果,
#1 = Methodref #34.#76 // com/stock/test/SpringTest."<init>":()V
#34 = Class #110 // com/stock/test/SpringTest
#76 = NameAndType #35:#36 // "<init>":()V
可以看到javap出来的结果第一行就是索引为1的常量项,它有类型是Methodref,后面的#34和#76表示由这2项组成,具体的值就是:
com/stock/test/SpringTest."<init>":()V
前面是类名好理解;
后面方法描述符"<init>":()V,这里双引号之间是方法名,后面的()表示参数,这里为空表示没有参数,最后一个V表示返回值,完整的类型表示有:
再往下分析,接下来是08,表示类型为CONSTANT_String,这种类型接下来2字节表示字符串值的索引,这里为004D,即77,即它指向索引77的常量项,77项,77项的位置在哪呢,在偏移量为0x328的位置:
这里第1个字节是1,表示类型为CONSTANT_Utf8,接下是2字节是0006,表示这个字符串的字节长度是6,再接来是是6字节的具体内容,即E4B8ADE59BBD,这个就是字符串“中国”的UTF8表示了。
再看其它几种类型的,索引为16的,对应的偏移量为0X47
类型为0B,即11,对应的是CONSTANT_InterfaceMethodref;
接下来2字节是005C和005D,表示对应的接口名的索引为92,接口方法描述索引为93,因为我们有这么一段代码:
pst.addBatch(sql);
92和93索引联合的内容为:java/sql/PreparedStatement.addBatch:(Ljava/lang/String;)V
其中()之间为Ljava/lang/String;,这里以;结束便于多个类型串联起来阅读,前面说过L开头的表示对象,这里表示参数的类型为String,返回值为void,对应addBatch方法原型为:
void addBatch( String sql ) throws SQLException;