
> 2020年10月份,《Java虛擬機規范(Java SE 8版)》讀書筆記
[TOC]
## 1 概述
本文主要介紹 [javac](http://wiki.baidu.com/pages/viewpage.action?pageId=1185593218) 編譯 `*.java` 文件后,生成的 `*.class` 文件的結構。
### 1.1 unsigned byte 解析時的轉換
class 文件中使用 unsigned byte,也就是說解析時需要將 byte 轉換成 short 表示,轉換方法是:
```java
byte rawValue = -2;
short value = (short) ((short) rawValue & 0xFF); // 254
```
例如在 class 文件中的 2,解析時會表示成 2,class 文件中的 -2,解析時便表示成 254;
### 1.2 ClassFile 結構
每一個 class 文件對應一個如下的 ClassFile 結構,如下所示。
其中 u2 表示 2 個 unsigned byte,u4 表示 4 個 unsigned byte,以此類推;
每行注釋中,第一個中括號里面的內容表示數組的長度,第二個中括號里面表示該字段的名稱,剩下的內容是該字段的含義。
```
/** [u4] [magic] 魔數(必須為十六進制CABEBABE) */
byte[] magic;
/** [u2] [minor_version] 次版本號 */
byte[] minorVersion;
/** [u2] [major_version] 主版本號 */
byte[] majorVersion;
/** [u2] [constant_pool_count] 常量池的長度 */
byte[] constPoolCount;
/** [constant_pool_count-1] [constant_pool] 常量池 */
ConstInfo[] constPool;
/** [u2] [access_flags] 訪問標記 */
byte[] accessFlags;
/** [u2] [this_class] this 類的常量池索引 */
byte[] thisClass;
/** [u2] [super_class] super 類的常量池索引 */
byte[] superClass;
/** [u2] [interfaces_count] 接口列表的長度 */
byte[] interfacesCount;
/** [interfaces_count] [interfaces] 接口列表 */
InterfaceTable[] interfaces;
/** [u2] [fields_count] 字段列表的長度 */
byte[] fieldsCount;
/** [fields_count] [fields] 字段列表 */
FieldTable[] fields;
/** [u2] [methods_count] 方法列表的長度 */
byte[] methodsCount;
/** [methods_count] [methods] 方法列表 */
MethodTable[] methods;
/** [u2] [attributes_count] 屬性列表的長度 */
byte[] attributesCount;
/** [attributes_count] [attributes] 屬性列表 */
Attr[] attributes;
```
## 2 ClassFile 解析程序
筆者根據規范以及《深入理解Java虛擬機(第3版)》,編寫了 ByteCode.java 程序,實現了 class 文件的解析。
### 2.1 程序的初始化
magic、minorVersion 等長度都是固定的,在 ByteCode 初始化時便對其進行實例化。content 持有字節流的引用,pos 則表示當前讀取到的位置。
```java
/** class 無符號字節數組的引用 */
private final byte[] content;
/** 讀取 {@link #content} 時的當前位置 */
private int pos = 0;
/**
* 構造方法
*
* @param unsignedBytes 通過任意形式傳入的無符號字節數組
*/
public ByteCode(byte[] unsignedBytes) {
magic = new byte[U4];
minorVersion = new byte[U2];
majorVersion = new byte[U2];
constPoolCount = new byte[U2];
accessFlags = new byte[U2];
thisClass = new byte[U2];
superClass = new byte[U2];
interfacesCount = new byte[U2];
fieldsCount = new byte[U2];
methodsCount = new byte[U2];
attributesCount = new byte[U2];
this.content = unsignedBytes;
}
```
### 2.2 各結構的解析過程總覽
在進行初始化后,根據規范,按照順序解析字節碼,并對 pos 同步更改。
```java
/**
* 嚴格地按照順序解析字節碼
*/
public void parseByteCode() {
pos += read(content, pos, magic); // 讀取 魔數
pos += read(content, pos, minorVersion); // 讀取 次版本號
pos += read(content, pos, majorVersion); // 讀取 主版本號
pos += read(content, pos, constPoolCount); // 讀取 常量池的長度
readConstPoolInfo(); // 讀取 常量池
pos += read(content, pos, accessFlags); // 讀取 訪問標記
pos += read(content, pos, thisClass); // 讀取 this
pos += read(content, pos, superClass); // 讀取 super
pos += read(content, pos, interfacesCount); // 讀取 接口列表的長度
readInterfaces(); // 讀取 接口列表
pos += read(content, pos, fieldsCount); // 讀取 字段列表的長度
readFields(); // 讀取 字段列表
pos += read(content, pos, methodsCount); // 讀取 方法列表的長度
readMethods(); // 讀取 方法列表
pos += read(content, pos, attributesCount); // 讀取 屬性列表的長度
readAttributes(); // 讀取 屬性列表
if (pos != content.length) { // 讀取完畢后校驗是否按照JVM規范讀取完畢
throw new IllegalStateException("解析的數據與規范不符!");
}
}
```
read 方法的作用是拷貝讀取的內容到 target 數組,并返回讀取的長度,以對 pos 更新
```java
/**
* 滾動地讀取字節,從 {@param source} 滾動讀取字節,填充到 {@param target} 中
*
* @param source 數據源
* @param sourcePos 數據源的開始下標
* @param target 目標字節數組
* @return 返回讀取的字節數
*/
public static int read(byte[] source, int sourcePos, byte[] target) {
int targetLength = target.length;
System.arraycopy(source, sourcePos, target, 0, targetLength);
return targetLength;
}
```
接下來,還需讀取以下內容:
- readConstPoolInfo
- readFields
- readMethods
- readAttributes
## 3 讀取常量池(constant_pool)
### 3.1 tag 位
常量池表中的所有的項具有如下的通用格式:
```java
cp_info {
u1 tag;
u1 info[];
}
```
其中 tag 對應規則如下:
```
int TAG_UTF8 = 1;
int TAG_INTEGER = 3;
int TAG_FLOAT = 4;
int TAG_LONG = 5;
int TAG_DOUBLE = 6;
int TAG_CLASS = 7;
int TAG_STRING = 8;
int TAG_FIELDREF = 9;
int TAG_METHODREF = 10;
int TAG_INTERFACE_METHODREF = 11;
int TAG_NAME_AND_TYPE = 12;
int TAG_METHOD_HANDLE = 15;
int TAG_METHOD_TYPE = 16;
int TAG_DYNAMIC = 17;
int TAG_INVOKE_DYNAMIC = 18;
int TAG_MODULE = 19;
int TAG_PACKAGE = 20;
```
### 3.2 常量池解析過程總覽
常量池的解析過程,總結下來,分為以下幾步:
- 獲取常量池項的長度
- 根據其長度,遍歷去識別每個 tag ,再對每個類型的 tag 去作對應的解析
以下分別是獲取常量池長度的實現,和遍歷讀取常量池項的實現。
```java
/**
* 返回常量池長度的數值表示
*
* @param constPoolCount 表示常量池長度的字節數組
* @return 常量池長度的數值表示。常量池不同于Java語言習慣,是從1開始計數的。假設常量池長度為2,則只包含1,2兩個常量項
*/
private static int valueOfConstPoolLength(byte[] constPoolCount) {
return unsignedBytes2Int(constPoolCount) - 1;
}
/**
* 讀取常量池
*/
private void readConstPoolInfo() {
constPool = new ConstInfo[valueOfConstPoolLength(constPoolCount)];
for (int i = 0; i < constPool.length; i++) {
byte[] tag = new byte[U1]; // tag u1
pos += Util.read(content, pos, tag);
int tagValue = unsignedBytes2Int(tag);
switch (tagValue) {
case IConstPoolInfo.TAG_UTF8:
constPool[i] = new Utf8Const(content, pos, tagValue);
break;
case IConstPoolInfo.TAG_INTEGER:
constPool[i] = new IntegerConst(content, pos, tagValue);
break;
case IConstPoolInfo.TAG_FLOAT:
constPool[i] = new FloatConst(content, pos, tagValue);
break;
case IConstPoolInfo.TAG_LONG:
constPool[i] = new LongConst(content, pos, tagValue);
break;
case IConstPoolInfo.TAG_DOUBLE:
constPool[i] = new DoubleConst(content, pos, tagValue);
break;
case IConstPoolInfo.TAG_CLASS:
constPool[i] = new ClassConst(content, pos, tagValue);
break;
case IConstPoolInfo.TAG_STRING:
constPool[i] = new StringConst(content, pos, tagValue);
break;
case IConstPoolInfo.TAG_FIELDREF:
constPool[i] = new FieldrefConst(content, pos, tagValue);
break;
case IConstPoolInfo.TAG_METHODREF:
constPool[i] = new MethodrefConst(content, pos, tagValue);
break;
case IConstPoolInfo.TAG_INTERFACE_METHODREF:
constPool[i] = new InterfaceMethodrefConst(content, pos, tagValue);
break;
case IConstPoolInfo.TAG_NAME_AND_TYPE:
constPool[i] = new NameAndTypeConst(content, pos, tagValue);
break;
case IConstPoolInfo.TAG_METHOD_HANDLE:
constPool[i] = new MethodHandleConst(content, pos, tagValue);
break;
case IConstPoolInfo.TAG_METHOD_TYPE:
constPool[i] = new MethodTypeConst(content, pos, tagValue);
break;
case IConstPoolInfo.TAG_DYNAMIC:
constPool[i] = new DynamicConst(content, pos, tagValue);
break;
case IConstPoolInfo.TAG_INVOKE_DYNAMIC:
constPool[i] = new InvokeDynamicConst(content, pos, tagValue);
break;
case IConstPoolInfo.TAG_MODULE:
constPool[i] = new ModuleConst(content, pos, tagValue);
break;
case IConstPoolInfo.TAG_PACKAGE:
constPool[i] = new PackageConst(content, pos, tagValue);
break;
default:
throw new IllegalStateException("無效tag:" + tagValue);
}
pos += constPool[i].getOffset();
}
}
```
以下是常量池各項的結構:
```txt
CONSTANT_Utf8_info {
u1 tag; // 標志位,必須為 1
u2 utf8_string_length; // Utf8 編碼的字符串的長度
utf8_string_length utf8_string; // Utf8 編碼的字符串
}
CONSTANT_Integer_info {
u1 tag; // 標志位,必須為 3
u4 integer_value; // Integer 類型的數值表示
}
CONSTANT_Float_info {
u1 tag; // 標志位,必須為 4
u4 float_value; // Float 類型的數值表示
}
CONSTANT_Long_info {
u1 tag; // 標志位,必須為 5
u8 long_value; // Long 類型的數值表示
}
CONSTANT_Double_info {
u1 tag; // 標志位,必須為 6
u8 double_value; // Double 類型的數值表示
}
CONSTANT_Class_info {
u1 tag; // 標志位,必須為 7
u2 class_name_index; // 指向全限定名常量項的索引
}
CONSTANT_String_info {
u1 tag; // 標志位,必須為 8
u2 string_literal_index; // 指向字符串字面量的常量池索引
}
CONSTANT_Fieldref_info {
u1 tag; // 標志位,必須為 9
u2 class_info_index; // 指向聲明方法的類描述符 CONSTANT_Class_info 的常量池索引
u2 name_and_type_info_index; // 指向字段描述符 CONSTANT_NameAndType_info 的常量池索引
}
CONSTANT_Methodref_info {
u1 tag; // 標志位,必須為 10
u2 class_info_index; // 指向聲明方法的類描述符 CONSTANT_Class_info 的常量池索引
u2 name_and_type_info_index; // 指向字段描述符 CONSTANT_NameAndType_info 的常量池索引
}
CONSTANT_InterfaceMethodref_info {
u1 tag; // 標志位,必須為 11
u2 class_info_index; // 指向聲明方法的類描述符 CONSTANT_Class_info 的常量池索引
u2 name_and_type_info_index; // 指向字段描述符 CONSTANT_NameAndType_info 的常量池索引
}
CONSTANT_NameAndType_info {
u1 tag; // 標志位,必須為 12
u2 name_index; // 指向該字段或方法 名稱常量項 的常量池索引
u2 descriptor_index; // 指向該字段或方法 描述符常量項 的常量池索引
}
CONSTANT_MethodHandle_info {
u1 tag; // 標志位,必須為 15
u1 reference_kind_value; // 值區間必須=為[1, 9],決定了方法句柄的類型(kind),方法句柄類型的值表示方法句柄的字節碼行為
u2 reference_index; // 根據 reference_kind_value 選擇常量池項的類型
}
CONSTANT_MethodType_info {
u1 tag; // 標志位,必須為 16
u2 descriptor_index; // 方法的描述符,值必須是對常量池的有效索引,常量池在該索引處的類型必須是 CONSTANT_Utf8_info 結構
}
CONSTANT_Dynamic_info {
u1 tag; // 標志位,必須為 17
u2 bootstrap_method_attr_index; // 值必須是對當前Class文件中引導方法表的bootstrap_methods[]數組的有效索引
u2 name_and_type_index; // 指向方法名和方法描述符 CONSTANT_NameAndType_info 的常量池索引
}
CONSTANT_InvokeDynamic_info {
u1 tag; // 標志位,必須為 18
u2 bootstrap_method_attr_index; // 值必須是對當前Class文件中引導方法表的bootstrap_methods[]數組的有效索引
u2 name_and_type_index; // 指向方法名和方法描述符 CONSTANT_NameAndType_info 的常量池索引
}
CONSTANT_Module_info {
u1 tag; // 標志位,必須為 19
u2 module_name_index; // 模塊名稱,必須是對常量池的有效索引,常量池在該項的類型必須是 CONSTANT_Utf8_info 結構
}
CONSTANT_Package_info {
u1 tag; // 標志位,必須為 20
u2 package_name_index; // 包的名稱,必須是對常量池的有效索引,常量池在該項的類型必須是 CONSTANT_Utf8_info 結構
}
```
> TODO:對 CONSTANT_MethodHandle_info 的 reference_index 取值進行更詳細的介紹
### 3.3 常量池項解析舉例
本節將以 CONSTANT_Utf8_info 和 CONSTANT_Float_info 為例,說明該程序對常量池項的解析過程。
--------
當 `tag == 1` 時,表示 UTF8 信息,其結構為:
```
CONSTANT_Utf8_info {
u1 tag; // 標志位,必須為 1
u2 utf8_string_length; // Utf8 編碼的字符串的長度
utf8_string_length utf8_string; // Utf8 編碼的字符串
}
```
解析時,會通過如下的 Utf8Const 類:
```
import vm.clazz.Util;
/**
* 常量池信息 - UTF8 類型
*/
public final class Utf8Const extends ConstInfo {
/** Utf8編碼的字符串的長度 u2 */
private final byte[] length;
/** Utf8編碼的字符串 */
private final byte[] utf8String;
/**
* 構造方法
*
* @param content 字節數組
* @param pos 當前讀取的位置
* @param tag 常量池 tag 位
*/
public Utf8Const(byte[] content, int pos, int tag) {
super(tag);
length = new byte[U2];
pos += Util.read(content, pos, length);
utf8String = new byte[Util.unsignedBytes2Int(length)];
pos += Util.read(content, pos, utf8String);
}
@Override
public int getTag() {
return TAG_UTF8;
}
@Override
public String getType() {
return TYPE_UTF8;
}
@Override
public long getOffset() {
return length.length + utf8String.length;
}
// ...
}
```
------------
當 `tag == 4` 時,表示 Float 信息,其結構為:
```java
CONSTANT_Float_info {
u1 tag; // 標志位,必須為 4
u4 float_value; // Float 類型的數值表示
}
```
解析時,會通過如下的 FloatConst 類:
```
import vm.clazz.UnsignedByte;
import vm.clazz.Util;
/**
* 常量池信息 - Float 類型
*/
public final class FloatConst extends ConstInfo {
/** Float類型的數值表示 */
final byte[] floatValue;
public FloatConst(byte[] content, int pos, int tagValue) {
super(tagValue);
floatValue = new byte[U4];
pos += Util.read(content, pos, floatValue);
}
@Override
public int getTag() {
return TAG_FLOAT;
}
@Override
public String getType() {
return TYPE_FLOAT;
}
@Override
public long getOffset() {
return floatValue.length;
}
// ...
}
```
## 4 讀取 接口/字段/方法 列表(interfaces/fields/methods)
讀取接口列表的步驟分為以下幾步:
- 讀取接口列表的長度
- 根據接口列表的長度,依次讀取每個接口表
```
/**
* 讀取接口列表
*/
private void readInterfaces() {
int interfacesCountValue = (int) valueOf(interfacesCount);
interfaces = new InterfaceTable[interfacesCountValue];
for (int i = 0; i < interfaces.length; i++) {
interfaces[i] = new InterfaceTable(content, pos);
pos += interfaces[i].getOffset();
}
}
```
每個接口表的內容其實只有表示接口名稱的常量池索引:
```
interface_info {
u2 interface_name_index; // 接口名稱的常量池索引
}
```
其讀取與常量池項的讀取類似,程序如下所示:
```
import vm.clazz.IConstPoolInfo;
import vm.clazz.Util;
import vm.clazz.cp.SeqRead;
/**
* 接口表
*/
public class InterfaceTable implements IConstPoolInfo, SeqRead {
/** 接口名稱的常量池索引 */
final byte[] nameIndex;
/**
* 構造方法
*
* @param content 字節數組
* @param pos 當前讀取的位置
*/
public InterfaceTable(byte[] content, int pos) {
nameIndex = new byte[U2];
pos += Util.read(content, pos, nameIndex);
}
@Override
public long getOffset() {
return nameIndex.length;
}
}
```
--------
讀取字段列表的步驟分為以下幾步:
- 讀取字段列表的長度
- 根據字段列表的長度,依次讀取每個字段表
每個字段表的內容包含以下幾項:
```
field_info {
u2 access_flag; // 訪問標志
u2 field_name_index; // 名稱 - 常量池索引
u2 descriptor_index; // 描述符 - 常量池索引
u2 attribute_count; // 屬性列表的長度
attribute_count attributes; // 屬性列表
}
```
--------
讀取方法列表的步驟分為以下幾步:
- 讀取方法列表的長度
- 根據方法列表的長度,依次讀取每個方法表
每個方法表的內容包含以下幾項:
```
method_info {
u2 access_flag; // 訪問標志
u2 method_name_index; // 名稱 - 常量池索引
u2 descriptor_index; // 描述符 - 常量池索引
u2 attribute_count; // 屬性列表的長度
attribute_count attributes; // 屬性列表
}
```
## 5 讀取屬性列表(attributes)
屬性表的通用格式如下:
```
attribute_info {
u2 attribute_name_index; // 屬性名稱的常量池索引
u4 attribute_length; // 字節形式的屬性內容的字節數
attribute_length info; // 字節形式的屬性內容
}
```
其解析程序如下:
```java
import vm.clazz.IConstPoolInfo;
import vm.clazz.Util;
import vm.clazz.cp.SeqRead;
/**
* 屬性表 (attribute_info)
*/
public class Attr implements SeqRead, IConstPoolInfo {
/** u2 屬性名稱 - 常量池索引 */
private final byte[] attributeNameIndex;
/** u4 屬性值所占用的位數 */
private final byte[] attributeLength;
/** u1 屬性的內容,長度為 attributeLength */
private final byte[] info;
public Attr(byte[] source, int sourcePos) {
attributeNameIndex = new byte[U2];
sourcePos += Util.read(source, sourcePos, attributeNameIndex);
attributeLength = new byte[U4];
sourcePos += Util.read(source, sourcePos, attributeLength);
info = new byte[Util.unsignedBytes2Int(attributeLength)];
sourcePos += Util.read(source, sourcePos, info);
}
@Override
public long getOffset() {
return attributeNameIndex.length + attributeLength.length + info.length;
}
}
```
而更具體地,可以將attribute分為以下類型:
屬性名稱 | 使用位置 | 含義
------------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------
Code | 方法表 | Java 代碼編譯成的字節碼指令
ConstantValue | 字段表 | 由 final 關鍵字定義的常量值
Deprecated | 類、方法表、字段表 | 被聲明為 deprecated 的類、方法和字段
Exceptions | 方法表 | 方法拋出的異常列表
EnclosingMethod | 類文件 | 僅當一個類為局部類或者匿名類時才能擁有這個屬性,用于標識這個類所在的外圍方法
InnerClasses | 類文件 | 內部類列表
LineNumberTable | Code 屬性 | Java 源碼的行號與字節碼指令的對應關系
LocalVariableTable | Code 屬性 | 方法的局部變量表描述
StackMapTable | Code 屬性 | (JDK 6)提供新的類型檢查驗證器檢查和處理目標方法的局部變量和操作數棧所需要的類型是否匹配
Signature | 類、方法表、字段表 | (JDK 5)用于支持泛型情況下的方法簽名,用于記錄泛型中的相關信息
SourceFile | 類 | 記錄源文件名稱
SourceDebugExtension | 類文件 | (JDK 5)用于存儲額外的調試信息。譬如在進行JSP調試時,無法通過Java堆棧來定位到JSP的行號,JSR 45提案為這些非Java語言、卻需要編譯成字節碼并運行在Java虛擬機中的程序提供了一個進行調試的標準機制,用于存儲調試信息
Synthetic | 類、方法表、字段表 | 標識方法或字段為編譯器自動生成的
LocalVariableTypeTable | 類 | (JDK 5)它使用特征簽名代替描述符,是為了引入泛型語法之后能描述泛型參數化類型而添加
RuntimeVisibleAnnotations | 類、方法表、字段表 | (JDK 5)為動態注解提供支持。該屬性用于指明哪些注解是運行時(實際運行時就是進行反射調用)可見的
RuntimeInvisibleAnnotations | 類、方法表、字段表 | (JDK 5)與RuntimeVisibleAnnotations剛好相反,指明哪些注解是運行時不可見的
RuntimeVisibleParameterAnnotations | 方法表 | (JDK 5)作用與RuntimeVisibleAnnotations類似,只不過作用對象為方法參數
RuntimeInvisibleParameterAnnotations | 方法表 | (JDK 5)作用與RuntimeVisibleParameterAnnotations相反
AnnotationDefault | 方法表 | (JDK 5)用于記錄注解類元素的默認值
BootstrapMethods | 類文件 | (JDK 7)用于保存invokedynamic指令引用的引導方法限定符
RuntimeVisibleTypeAnnotations | 類、方法表、字段表、Code 屬性 | (JDK 8)為實現JSR 308中新增的類型注解提供的支持,指明哪些類注解是運行時可見的
RuntimeInvisibleTypeAnnotations | 類、方法表、字段表、Code 屬性 | (JDK 8)與RuntimeVisibleTypeAnnotations屬性剛好相反
MethodParameters | 方法表 | (JDK 8)用于支持(加上-parameters參數)將方法參數名稱編譯進Class文件中,并可運行時獲取
Module | 類 | (JDK 9)用于記錄一個Module的名稱以及相關信息(requires、exports、opens、uses、provides)
ModulePackages | 類 | (JDK 9)用于記錄一個模塊中所有被export或者opens的包
ModuleMainClass | 類 | (JDK 9)用于指定一個模塊的主類
NestHost | 類 | (JDK 11)用于支持嵌套類(Java中的內部類)的反射和訪問控制的API,一個內部類通過該屬性得知自己的宿主類
NestMembers | 類 | (JDK 11)用于支持嵌套類(Java中的內部類)的反射和訪問控制的API,一個宿主類通過該屬性得知自己有哪些內部類
> TODO:解析出更具體的屬性(例如Code、ConstantValue等)
## 6 常量池項的格式化
### 6.1 Utf8 的格式化
即使含有中文字符,也可以通過以下方法將字節數組轉換成字符串形式:
```
public static String toUtf8String(byte[] utf8StringBytes) {
return new String(utf8StringBytes);
}
```
### 6.2 Integer / Long 的格式化
針對 CONSTANT_Integer_info 類型,首先對無符號字節數組進行處理,轉換成short,再按照 Integer 在二進制中的存儲形式,轉成十進制形式。
```java
public static int toInt(byte[] integerValueBytes) {
UnsignedByte[] ubs = UnsignedByte.from(integerValueBytes);
return (ubs[0].getValue() << (8 * 3))
+ (ubs[1].getValue() << (8 * 2))
+ (ubs[2].getValue() << (8 * 1))
+ (ubs[3].getValue() << (8 * 0));
}
```
CONSTANT_Long_info 類型的處理方式與 CONSTANT_Integer_info 類似:
```java
public static long toLong(byte[] longValueBytes) {
UnsignedByte[] bytesUB = UnsignedByte.from(longValueBytes);
return (bytesUB[0].getValue() << (8 * 7))
+ (bytesUB[1].getValue() << ((8 * 6)))
+ (bytesUB[2].getValue() << ((8 * 5)))
+ (bytesUB[3].getValue() << (8 * 4))
+ (bytesUB[4].getValue() << (8 * 3))
+ (bytesUB[5].getValue() << (8 * 2))
+ (bytesUB[6].getValue() << (8 * 1))
+ bytesUB[7].getValue();
}
```
### 6.3 Float / Double 的格式化
要解決 Float / Double 格式化的問題,首先要理解 float 和 double 在二進制中存儲的格式:
|| 符號位 | 階碼 | 尾數 | 長度 |
| ---- | ---- | ---- | ---- |
| float | 1 | 8 | 23 | 32 |
| double | 1 | 11 | 52 | 64 |
---
下面是一個將 float 二進制形式轉為十進制形式的例子:
`-25.125f` 的二進制形式:
```
11000001 11001001 00000000 00000000
```
按照 符號位-階碼-尾數 分隔為:
```
1 10000011 10010010000000000000000
```
- 其中 1 表示該浮點數為負數;
- 10000011 的十進制為:128 + 2 + 1 = 131,減去127((1 << 7) - 1)得到4,表示小數點右移的數位
- 剩下 23 位是純二進制小數
- 將10010010000000000000000表示成小數,為0.10010010000000000000000
- 前面加1得到1.10010010000000000000000
- 小數點右移4位得到11001.0010000000000000000
- 變為10進制,得到 (1 + 8 + 16).(1/8) = 25.125
前面的1表示負數,所以最終該浮點數的十進制表示為 `-25.125`
----
針對 CONSTANT_Float_info 類型,將其無符號字節數組轉換成十進制數值表示的方法如下:
```
/**
* 將二進制的float轉換成十進制數值
*
* @param floatValueBytes 4字節
* @return floatValueBytes的十進制數值表示
*/
public static float parseFloat(byte[] floatValueBytes) {
int data = (int) UnsignedByte.valueOf(floatValueBytes);
boolean positive = ((data & 0b10000000_00000000_00000000_00000000) >> 31) == 0; // 第1位
int exponent = ((data & 0b01111111_10000000_00000000_00000000) >> 23) - 127; // 階碼
int mantissa = (data & 0b00000000_11111111_11111111_11111111); // & 0b00000000_01111111_11111111_11111111 后,首位"+1"
int left = mantissa >> (23 - exponent);
int right = mantissa << (32 - 23 + exponent) >> (32 - 23 + exponent);
int dividend = calcDividend(right, 23 - exponent);
float floatValue = (left + 1.0f / dividend);
return positive ? floatValue : -floatValue;
}
/**
* 計算小數點后的二進制的十進制表示,如.001表示8,再用1/8得到0.125
*/
private static int calcDividend(int rightBits, int len) {
int result = 0;
while (rightBits != 0) {
int lastBits = rightBits & 1; // 最后一位是1,則結果為1,否則為0
result += lastBits * Math.pow(2, len);
rightBits >>= 1;
len--;
}
return result;
}
```
----
CONSTANT_Double_info 與 CONSTANT_Float_info 類似。
## 7 TODO事項
- 對 CONSTANT_MethodHandle_info 的 reference_index 取值進行更詳細的介紹
- 解析出更具體的屬性(例如Code、ConstantValue等)
- 擴展到dex文件結構的解析
- 空白目錄
- 精簡版Spring的實現
- 0 前言
- 1 注冊和獲取bean
- 2 抽象工廠實例化bean
- 3 注入bean屬性
- 4 通過XML配置beanFactory
- 5 將bean注入到bean
- 6 加入應用程序上下文
- 7 JDK動態代理實現的方法攔截器
- 8 加入切入點和aspectj
- 9 自動創建AOP代理
- Redis原理
- 1 Redis簡介與構建
- 1.1 什么是Redis
- 1.2 構建Redis
- 1.3 源碼結構
- 2 Redis數據結構與對象
- 2.1 簡單動態字符串
- 2.1.1 sds的結構
- 2.1.2 sds與C字符串的區別
- 2.1.3 sds主要操作的API
- 2.2 雙向鏈表
- 2.2.1 adlist的結構
- 2.2.2 adlist和listNode的API
- 2.3 字典
- 2.3.1 字典的結構
- 2.3.2 哈希算法
- 2.3.3 解決鍵沖突
- 2.3.4 rehash
- 2.3.5 字典的API
- 2.4 跳躍表
- 2.4.1 跳躍表的結構
- 2.4.2 跳躍表的API
- 2.5 整數集合
- 2.5.1 整數集合的結構
- 2.5.2 整數集合的API
- 2.6 壓縮列表
- 2.6.1 壓縮列表的結構
- 2.6.2 壓縮列表結點的結構
- 2.6.3 連鎖更新
- 2.6.4 壓縮列表API
- 2.7 對象
- 2.7.1 類型
- 2.7.2 編碼和底層實現
- 2.7.3 字符串對象
- 2.7.4 列表對象
- 2.7.5 哈希對象
- 2.7.6 集合對象
- 2.7.7 有序集合對象
- 2.7.8 類型檢查與命令多態
- 2.7.9 內存回收
- 2.7.10 對象共享
- 2.7.11 對象空轉時長
- 3 單機數據庫的實現
- 3.1 數據庫
- 3.1.1 服務端中的數據庫
- 3.1.2 切換數據庫
- 3.1.3 數據庫鍵空間
- 3.1.4 過期鍵的處理
- 3.1.5 數據庫通知
- 3.2 RDB持久化
- 操作系統
- 2021-01-08 Linux I/O 操作
- 2021-03-01 Linux 進程控制
- 2021-03-01 Linux 進程通信
- 2021-06-11 Linux 性能優化
- 2021-06-18 性能指標
- 2022-05-05 Android 系統源碼閱讀筆記
- Java基礎
- 2020-07-18 Java 前端編譯與優化
- 2020-07-28 Java 虛擬機類加載機制
- 2020-09-11 Java 語法規則
- 2020-09-28 Java 虛擬機字節碼執行引擎
- 2020-11-09 class 文件結構
- 2020-12-08 Java 內存模型
- 2021-09-06 Java 并發包
- 代碼性能
- 2020-12-03 Java 字符串代碼性能
- 2021-01-02 ASM 運行時增強技術
- 理解Unsafe
- Java 8
- 1 行為參數化
- 1.1 行為參數化的實現原理
- 1.2 Java 8中的行為參數化
- 1.3 行為參數化 - 排序
- 1.4 行為參數化 - 線程
- 1.5 泛型實現的行為參數化
- 1.6 小結
- 2 Lambda表達式
- 2.1 Lambda表達式的組成
- 2.2 函數式接口
- 2.2.1 Predicate
- 2.2.2 Consumer
- 2.2.3 Function
- 2.2.4 函數式接口列表
- 2.3 方法引用
- 2.3.1 方法引用的類別
- 2.3.2 構造函數引用
- 2.4 復合方法
- 2.4.1 Comparator復合
- 2.4.2 Predicate復合
- 2.4.3 Function復合
- 3 流處理
- 3.1 流簡介
- 3.1.1 流的定義
- 3.1.2 流的特點
- 3.2 流操作
- 3.2.1 中間操作
- 3.2.2 終端操作
- 3.3.3 構建流
- 3.3 流API
- 3.3.1 flatMap的用法
- 3.3.2 reduce的用法
- 3.4 collect操作
- 3.4.1 collect示例
- 3.4.2 Collector接口