go實現java虛擬機03

来源:https://www.cnblogs.com/wyq1995/archive/2020/03/01/12381052.html
-Advertisement-
Play Games

上一篇我們已經根據路徑讀取到了我們需要的位元組碼文件,就以java.lang.Object這個類為例,可以看到類似下麵這種東西,那麼這些數字是什麼呢? 要瞭解這個,我們大概可以猜到這是十進位的,線上將十進位轉為十六進位看看https://tool.oschina.net/hexconvert/,註意上 ...


  上一篇我們已經根據路徑讀取到了我們需要的位元組碼文件,就以java.lang.Object這個類為例,可以看到類似下麵這種東西,那麼這些數字是什麼呢?

 

  要瞭解這個,我們大概可以猜到這是十進位的,線上將十進位轉為十六進位看看https://tool.oschina.net/hexconvert/,註意上圖中已經用空格隔開了每個數,我們將最前面的變成十六進位看看效果,202對應CA,254對應FE,186對應BA,190對應BE,合起來就是CAFEBABE,有興趣的可以查查這代表的時一種咖啡,所有的符合jvm規範的位元組碼文件都是以這個開頭,專業稱呼 "魔數";

  不知道大家有沒有發現,如果我們分析這個的時候要自己一個一個的轉換,簡直太坑爹了,但是有很多工具可以幫助我們更好的看十六進位的,比如vscode,editplus,winhex,jclasslib(這個看不到十六進位,但是可以看位元組碼文件的結構),實在不想下載的其他東西話用vim也可以看十六進位;這裡強烈推薦一款工具叫做classpy,這個工具可以同時看十六進位和class位元組碼文件的結構,用起來很舒服;

  鏈接:https://pan.baidu.com/s/1s_fqLxQjG0lVXMEB5z1mlg  提取碼:gmyt  ,使用這個classpy的時候,但是有一個前提,你電腦必須要有gradle環境!!!首先解壓,然後需要進入classpy-master文件夾,命令行運行gradle uberjar,最後就是gradle run  ,以後每次的話直接使用gradle run就行了!打開ui界面之後,把class手動丟進去就行了,如下圖,左邊是class文件的結構,右邊的對應的十六進位;

 

 1.簡單說說class文件結構

  首先說說class位元組碼文件的結構,看有哪幾部分組成,其實在上圖左邊已經差不多說明瞭,下圖更清楚:其中u2表示兩個位元組,u4表示四個位元組,這之外的比如cp_info表示的是一張表,然後表中每一個欄位又對應著一張表(這麼說肯定不好理解,見過多維數組沒,表就看作數組就好,只不多數組每個位置又對應這一個數組,這就叫多維數組);

  至於下麵這些代表什麼意思,這裡 就不多做贅述了,自己去看位元組碼文件的組成吧,不是我們的重點;

 

  這裡的結構有個很有意思的現象,就是在列出該項數據之前,會提前指明該數據有幾個位元組;比如constant_pool_count表示常量池中有n個表,占用2個位元組;而緊接其後的constant_pool[constant_pool_count-1]存的就是各個表實際的數據,由於每個表第一個位元組表示該表的類型,然後後面又會指定該表的大小,所以可以確定總共占用多少位元組;access_flags表示訪問許可權,占兩個位元組,等等

  接下來說說常量池中表的類型以及每個表的結構(每一種表都標識了自己占用的位元組大小),如下所示,每一種表都有自己特有的結構,還要註意一點,下麵這麼多表中,某一個表中某一項可能會引用另外一張表的數據的;

 

 

   常量池之外每個部分表示的什麼,我隨便找了一篇博客,參考這篇說的比較仔細的:https://www.jianshu.com/p/247e2475fc3a;這就不多說了,這也不是我們的重點;

 

2.讀取class位元組碼文件

  總的目錄結構如下所示:

   根據上面這個圖我們將classfile中的文件分為幾個部分理解一下,首先是class_reader.go這個文件裡面是結構體,存了class文件的全部數據的位元組切片,並且定義了一些方法一下子讀取1位元組,2位元組,4位元組和8位元組等方法,方便於我們讀取數據;

  然後class_file.go文件中一個結構體,存了位元組碼中所有結構,就是魔數,版本號,常量池,訪問修飾符等等,然後定義了一些獲取這些部分的方法,可想而知這些方法需要使用前面說的class_reader.go文件中結構體讀取數據;

  再然後比較關鍵,就是class_file.go文件中定義的那些獲取各個部分的方法,下圖所示,其中最關鍵的就是讀取常量池屬性表

 

  說道讀取常量池數據,那麼因為常量池中有很多不同類型的表,我們定義一個介面,所有的表都必須實現這個介面;至於總共有些什麼類型的表,大致分為兩種,一種是字元型,一種是引用型的;字元型的分為字元串和數字類的,分別是在上面的cp_utf8.go和cp_numberic.go中,其他的以cp開頭的都是引用類型的表;

  在讀取常量池中的表的時候,我們首先要確定正在讀取表的類型,在讀取第一個位元組的時候,該位元組就是說明該表示什麼類型,如下所示,然後每一種表都規定了位元組的結構,前面已經說明白了;

const (
    CONSTANT_Utf8               = 1
    CONSTANT_Integer            = 3
    CONSTANT_Float              = 4
    CONSTANT_Long               = 5
    CONSTANT_Double             = 6
    CONSTANT_Class              = 7
    CONSTANT_String             = 8
    CONSTANT_Fieldref           = 9
    CONSTANT_Methodref          = 10
    CONSTANT_InterfaceMethodref = 11
    CONSTANT_NameAndType        = 12
    CONSTANT_MethodHandle       = 15
    CONSTANT_MethodType         = 16
    CONSTANT_InvokeDynamic      = 18
)

 

 

  然後就是屬性表,其實和常量池差不多定義了一個頂層介面,只不過屬性表這裡不是用這種數字來決定表的類型,而是用屬性名(也就是字元串來區分),所以我們可以看到下麵這種結構,通過讀取屬性表前面兩個位元組找到常量池的Constant_Utf8表的索引,然後取到字元串,再到下麵這個switch中確定是什麼類型的屬性表;

 

  屬性表也有很多類型,我們這裡只是列舉其中的8種,至於每一種是什麼意思,看看這個博客:https://www.cnblogs.com/lrh-xl/p/5351182.html,在上面的目錄中attr_xxx開頭的都是屬性表,

 

3.各個文件

  class_reader.go:用於幫助我們讀取位元組切片中的數據:

package classfile

import "encoding/binary"

//這個結構體從位元組數組中讀取數據
type ClassReader struct {
    data []byte
}

//讀取一個位元組,而且data數據也要將第一個位元組幹掉
func (this *ClassReader) readUint8() uint8 { //u1
    val := this.data[0]
    this.data = this.data[1:]
    return val
}

//讀取兩個位元組
func (this *ClassReader) readUint16() uint16 { //u2
    val := binary.BigEndian.Uint16(this.data)
    this.data = this.data[2:]
    return val

}

//讀取四個位元組
func (this *ClassReader) readUint32() uint32 { //u4
    val := binary.BigEndian.Uint32(this.data)
    this.data = this.data[4:]
    return val

}

//讀取8個位元組
func (this *ClassReader) readUint64() uint64 {
    val := binary.BigEndian.Uint64(this.data)
    this.data = this.data[8:]
    return val

}

//讀取最前面的兩個位元組,表示數量
//根據這個數量繼續往後面讀取n個uint16的位元組
func (this *ClassReader) readUint16s() []uint16 {
    n := this.readUint16()
    s := make([]uint16, n)
    for i := range s {
        s[i] = this.readUint16()
    }
    return s
}

//獲取指定數量的位元組
func (this *ClassReader) readBytes(length uint32) []byte {
    bytes := this.data[:length]
    this.data = this.data[length:]
    return bytes

}
View Code

 

  class_file.go:定義了位元組碼文件的結構

package classfile

import "fmt"

//這個結構體就是體現了class文件的內容
type ClassFile struct {
    magic        uint32          //魔數 u4
    minorVersion uint16          //次版本號 u2
    majorVersion uint16          //主版本號 u2
    constantPool ConstantPool    //常量池
    accessFlags  uint16          //修飾符
    thisClass    uint16          //當前類
    superClass   uint16          //父類
    interfaces   []uint16        //介面,木有介面的數組
    fields       []*MemberInfo   //欄位
    methods      []*MemberInfo   //方法
    attributes   []AttributeInfo //屬性,例如全類名就是保存在這裡
}

//這個方法就是將byte數組解析成FileClass結構體
func Parse(classData []byte) (cf *ClassFile, err error) {
    //defer和recover模式,類似於java中的finally,這裡就是做一個異常不火再進行處理
    defer func() {
        if r := recover(); r != nil {
            var ok bool
            err, ok = r.(error)
            if !ok {
                err = fmt.Errorf("%v", r)
            }

        }
    }()
    //實例化一個ClassFile實例,用於保存位元組碼各個部分信息
    cf = &ClassFile{}
    //實例化一個class文件解析器,將存有位元組碼文件所有信息的數組傳遞進去
    cr := &ClassReader{classData}
    //read方法開始解析class文件各個部分的數據
    cf.read(cr)
    return
}

//這個方法就是按照位元組碼文件中各部分的順序進行讀取
func (this *ClassFile) read(reader *ClassReader) {
    this.readAndCheckMagic(reader)
    this.readAndCheckVersion(reader)
    this.constantPool = readConstantPool(reader)
    this.accessFlags = reader.readUint16()
    this.thisClass = reader.readUint16()
    this.superClass = reader.readUint16()
    this.interfaces = reader.readUint16s()
    this.fields = readMembers(reader, this.constantPool)
    this.methods = readMembers(reader, this.constantPool)
    this.attributes = readAttributes(reader, this.constantPool)
}

//獲取魔數,魔數是占有4個位元組
func (this *ClassFile) readAndCheckMagic(reader *ClassReader) {
    magic := reader.readUint32()
    //註意,所有符合jvm規範的魔數都是CAFEBABE,不符合條件的直接panic終止程式
    if magic != 0xCAFEBABE {
        panic("java.lang.ClassFormatError:magic")
    }
}

//次版本號和主版本號都是兩個位元組
//次版本號在jdk1.2之後就沒有用過了,都是0
//主版本號的版本從1.2開始是45,每次經過一個大的版本,就會+1,現在是52
func (this *ClassFile) readAndCheckVersion(reader *ClassReader) {
    this.minorVersion = reader.readUint16()
    this.majorVersion = reader.readUint16()
    switch this.majorVersion {
    case 45:
        return
    case 46, 47, 48, 49, 50, 51, 52:
        if this.minorVersion == 0 {
            return
        }
    }
    panic("java.lang.UnsupportedClassVersionError")
}

//獲取主版本號
func (this *ClassFile) MinorVersion() uint16 {
    return this.minorVersion
}

//獲取副版本號
func (this *ClassFile) MajorVersion() uint16 {
    return this.majorVersion
}

//獲取常量池
func (this *ClassFile) ConstantPool() ConstantPool {
    return this.constantPool
}

//獲取修飾符
func (this *ClassFile) AccessFlags() uint16 {
    return this.accessFlags
}

//從常量池中獲取類名
func (this *ClassFile) ClassName() string {
    return this.constantPool.getClassName(this.thisClass)
}

//從常量池中獲取超類名,註意,這裡需要判斷是不是Object類
func (this *ClassFile) SuperClassName() string {
    if this.superClass > 0 {
        return this.constantPool.getClassName(this.superClass)
    }
    return "" //這裡當類是Object的時候,那麼self.superClass為0
}

//獲取欄位
func (this *ClassFile) Fields() []*MemberInfo {
    return this.fields
}

//獲取方法
func (this *ClassFile) Methods() []*MemberInfo {
    return this.methods
}

//從常量池中找實現的所有介面名稱
func (this *ClassFile) InterfacesNames() []string {
    interfaceNames := make([]string, len(this.interfaces))
    for index, value := range this.interfaces {
        interfaceNames[index] = this.constantPool.getClassName(value)
    }
    return interfaceNames
}
View Code

  

  constant_pool.go:定義了一些方法幫助我們根據索引獲取各種表

package classfile

//這個介面表示常量池中每一張表
type ConstantInfo interface {
    readInfo(reader *ClassReader)
}

//常量池,其實就是所有類型表的切片
type ConstantPool []ConstantInfo

//用於讀取常量池中的表,將常量池中每張表解析之後放到這個切片中來,然後就可以根據索引獲取表數據了
//首先兩個位元組是常量池中表的個數cpCount,緊接著就是各種表的實際數據,每個表中第一個欄位表示了自己是什麼類型的表,
// 然後也已經規定好了自己所占位元組大小
//註意兩種表ConstantLongInfo和ConstantDoubleInfo,這種表示占兩個位置,其他類型的占用一個位置
//所以常量池中表實際的數量肯定是要小於cpCount
func readConstantPool(reader *ClassReader) ConstantPool {
    cpCount := int(reader.readUint16())
    cp := make([]ConstantInfo, cpCount)
    //註意,常量池遍歷從1開始,0表示不指向任何常量池數據
    for i := 1; i < cpCount; i++ {
        cp[i] = readConstantInfo(reader, cp)
        switch cp[i].(type) {
        case *ConstantLong, *ConstantDouble: //如果是這兩種類型的表,那麼在常量池中就占兩個位置
            i++
        }
    }
    return cp

}

//根據索引值獲取常量池中表
func (this ConstantPool) getConstantInfo(index uint16) ConstantInfo {
    if cpInfo := this[index]; cpInfo != nil {
        return cpInfo
    }
    panic("Invalid constant pool index!")

}

//根據索引從常量池中獲取某個ConstantNameAndTypeInfo表,然後獲取這張表的名字和描述
//註意,這個名字和描述分別又對應著常量池中的表
func (this ConstantPool) getNameAndType(index uint16) (string, string) {
    //這裡做了一個斷言,因為這裡沒有接收nil,所以如果失敗,直接panic
    ntInfo := this.getConstantInfo(index).(*ConstantNameAndTypeInfo)
    name := this.getUtf8(ntInfo.nameIndex)
    _type := this.getUtf8(ntInfo.descriptorIndex)
    return name, _type
}

//根據索引獲取常量池中ConstantClassInfo表,獲取該表的名字
//這個名字又對應常量池中一張ConstantUtf8Info表
func (this ConstantPool) getClassName(index uint16) string {
    classInfo := this.getConstantInfo(index).(*ConstantClassInfo)
    return this.getUtf8(classInfo.nameIndex)
}

//根據索引獲取常量池中的ConstantUtf8Info表,獲取其中保存的值
func (this ConstantPool) getUtf8(index uint16) string {
    utf8Info := this.getConstantInfo(index).(*ConstantUtf8Info)
    return utf8Info.str

}

//讀取常量池中的一個表,註意,不管是什麼表,它的第一個位元組tag表示表的類型
//我們這裡先獲取表的類型,然後實例化相應的表,最後調用該表實現的readInfo方法讀取表數據
func readConstantInfo(reader *ClassReader, constantPool ConstantPool) ConstantInfo {
    tag := reader.readUint8()
    info := newConstantInfo(tag, constantPool)
    info.readInfo(reader)
    return info
}

//下麵就是常量池中的所有類型,其中最下麵三種被註釋了,是因為這是在jdk7才被添加的,
// 為了支持新增的invokedynamic指令
//而且從下麵我們大概將常量池分為兩大類,字面量和符號引用;
//字面量:字元串常量和數字常量
//符號引用:類名,介面名,以及欄位和方法信息,為什麼叫做符號引用呢?因為這幾個表中沒有存實際的數據,
//存的都是指向常量池中ConstantUtf8Info表的索引
func newConstantInfo(tag uint8, constantPool ConstantPool) ConstantInfo {
    switch tag {
    case CONSTANT_Utf8:
        return &ConstantUtf8Info{}
    case CONSTANT_Integer:
        return &ConstantIntegerInfo{}
    case CONSTANT_Float:
        return &ConstantFloatInfo{}
    case CONSTANT_Long:
        return &ConstantLong{}
    case CONSTANT_Double:
        return &ConstantDouble{}
    case CONSTANT_Class:
        return &ConstantClassInfo{}
    case CONSTANT_String:
        return &ConstantStringInfo{}
    case CONSTANT_Fieldref:
        return &ConstantFieldrefInfo{}
    case CONSTANT_Methodref:
        return &ConstantMethodrefInfo{}
    case CONSTANT_InterfaceMethodref:
        return &ConstantInterfaceMethodrefInfo{}
    case CONSTANT_NameAndType:
        return &ConstantNameAndTypeInfo{}
    //case CONSTANT_MethodHandle:
    //    return &ConstantMethodHandleInfo{}
    //case CONSTANT_MethodType:
    //    return &ConstantMethodTypeInfo{}
    //case CONSTANT_InvokeDynamic:
    //    return &ConstantInvokeDynamic{}
    default:
        panic("java.lang.ClassFormatError: constant pool tag!")
    }

}
View Code

 

  constant_info.go:常量池中表的類型

package classfile

const (
    CONSTANT_Utf8               = 1
    CONSTANT_Integer            = 3
    CONSTANT_Float              = 4
    CONSTANT_Long               = 5
    CONSTANT_Double             = 6
    CONSTANT_Class              = 7
    CONSTANT_String             = 8
    CONSTANT_Fieldref           = 9
    CONSTANT_Methodref          = 10
    CONSTANT_InterfaceMethodref = 11
    CONSTANT_NameAndType        = 12
    CONSTANT_MethodHandle       = 15
    CONSTANT_MethodType         = 16
    CONSTANT_InvokeDynamic      = 18
)
View Code

  

  cp_utf8.go:

package classfile

type ConstantUtf8Info struct {
    str string
}

//CONSTANT_Utf8_info {
//u1 tag;
//u2 length;
//u1 bytes[length];
//}
//註意,這種表,第一個位元組表示表的類型,然後兩個位元組表示該表存的字元串的長度
//最後根據這個長度去讀取第三部分的數據,返回位元組切片,我們簡單的轉為字元串
func (this *ConstantUtf8Info) readInfo(reader *ClassReader) {
    length := uint32(reader.readUint16())
    bytes := reader.readBytes(length)
    this.str = string(bytes)
}
View Code

 

  cp_numberic.go:

package classfile

import (
    "math"
)

//該文件放四種與數字相關的表
//第一種表
type ConstantIntegerInfo struct {
    val int32
}

//實現了ConstantInfo介面,這種表第一個位元組表示類型,後面4個位元組表示存的數據
//CONSTANT_Integer_info {
//u1 tag;
//u4 bytes;
//}
func (this *ConstantIntegerInfo) readInfo(reader *ClassReader) {
    readUint32 := reader.readUint32()
    this.val = int32(readUint32)
}

//第二種表
type ConstantLong struct {
    val int64
}

//CONSTANT_Long_info {
//u1 tag;
//u4 high_bytes;
//u4 low_bytes;
//}
func (this *ConstantLong) readInfo(reader *ClassReader) {
    readUint64 := reader.readUint64()
    this.val = int64(readUint64)
}

//第三種表
type ConstantFloatInfo struct {
    val float32
}

//CONSTANT_Float_info {
//u1 tag;
//u4 bytes;
//}
func (this *ConstantFloatInfo) readInfo(reader *ClassReader) {
    readUint32 := reader.readUint32()
    //將uint32類型的轉為float32類型的
    this.val = math.Float32frombits(readUint32)
}

//第四種表
type ConstantDouble struct {
    val float64
}

//CONSTANT_Double_info {
//u1 tag;
//u4 high_bytes;
//u4 low_bytes;
//}
func (this *ConstantDouble) readInfo(reader *ClassReader) {
    readUint64 := reader.readUint64()
    this.val = math.Float64frombits(readUint64)
}
View Code

   

  cp_string.go

package classfile

//CONSTANT_String_info {
//u1 tag;
//u2 string_index;
//}
//這個表中沒有存數據,第一個位元組表示該表的類型,再之後的兩個位元組表示索引
// 這個索引表示指向常量池中ConstantUtf8Info表
type ConstantStringInfo struct {
    pool        ConstantPool
    stringIndex uint16
}

func (this *ConstantStringInfo) readInfo(reader *ClassReader) {
    this.stringIndex = reader.readUint16()
}

//獲取ConstantStringInfo對應的字元串
//在常量池中根據索引找到對應的ConstantUtf8Info表
func (this *ConstantStringInfo) String() string {
    return this.pool.getUtf8(this.stringIndex)
}
View Code

  

  cp_name_and_type.go

package classfile

//CONSTANT_NameAndType_info {
//u1 tag;
//u2 name_index;
//u2 descriptor_index;
//}
type ConstantNameAndTypeInfo struct {
    nameIndex       uint16
    descriptorIndex uint16
}

func (this *ConstantNameAndTypeInfo) readInfo(reader *ClassReader) {
    this.nameIndex = reader.readUint16()
    this.descriptorIndex = reader.readUint16()
}
View Code

   

  cp_member_ref.go

package classfile

//CONSTANT_Fieldref_info {
//u1 tag;
//u2 class_index;
//u2 name_and_type_index;
//}

type ConstantMemberrefInfo struct {
    pool             ConstantPool
    classIndex       uint16
    nameAndTypeIndex uint16
}

func (this *ConstantMemberrefInfo) readInfo(reader *ClassReader) {
    this.classIndex = reader.readUint16()
    this.nameAndTypeIndex = reader.readUint16()
}

func (this *ConstantMemberrefInfo) ClassName() string {
    return this.pool.getClassName(this.classIndex)
}

func (this *ConstantMemberrefInfo) NameAndDescriptor() (string, string) {
    return this.pool.getNameAndType(this.nameAndTypeIndex)
}

type ConstantFieldrefInfo struct {
    ConstantMemberrefInfo
}
type ConstantMethodrefInfo struct {
    ConstantMemberrefInfo
}
type ConstantInterfaceMethodrefInfo struct {
    ConstantMemberrefInfo
}
View Code

   

  cp_class.go

package classfile

//CONSTANT_Class_info {
//u1 tag;
//u2 name_index;
//}
type ConstantClassInfo struct {
    pool      ConstantPool
    nameIndex uint16
}

func (this *ConstantClassInfo) readInfo(reader *ClassReader) {
    this.nameIndex = reader.readUint16()
}

func (this *ConstantClassInfo) Name() string {
    return this.pool.getUtf8(this.nameIndex)
}
View Code

 

  member_info.go:方法表的欄位表都是一樣的,只是其中屬性表有點差異,所以可以用下麵這個結構體表示:

package classfile

//field_info {
//u2 access_flags;
//u2 name_index;
//u2 descriptor_index;
//u2 attributes_count;
//attribute_info attributes[attributes_count];
//}
//欄位表和方法表的結構幾乎是一樣的,只是屬性表不同,就用這個結構體表示
type MemberInfo struct {
    constPool       ConstantPool
    accessFlags     uint16          //訪問修飾符
    nameIndex       uint16          //欄位名
    descriptorIndex uint16          //欄位的類型
    attributes      []AttributeInfo //屬性表切片
}

//func (self *MemberInfo) AccessFlags() uint16 {...} // getter
//func (self *MemberInfo) Name() string {...}
//func (self *MemberInfo) Descriptor() string {...}

//因為欄位或者方法可能有多個,所以就遍歷進行讀取
func readMembers(reader *ClassReader, cp ConstantPool) []*MemberInfo {
    memberCount := reader.readUint16()
    infos := make([]*MemberInfo, memberCount)
    for index := range infos {
        infos[index] = readMember(reader, cp)
    }
    return infos
}

func readMember(reader *ClassReader, cp ConstantPool) *MemberInfo {
    return &MemberInfo{
        constPool:       cp,
        accessFlags:     reader.readUint16(),
        nameIndex:       reader.readUint16(),
        descriptorIndex: reader.readUint16(),
        attributes:      readAttributes(reader, cp),
    }
}

//根據索引獲取常量池中的ConstantUtf8Info表中存的欄位或者方法的字面量
func (this *MemberInfo) Name() string {
    return this.constPool.getUtf8(this.nameIndex)
}

//根據索引獲取常量池中的ConstantNameAndTypeInfo表中的欄位或者方法的描述
func (this *MemberInfo) Descriptor() string {
    return this.constPool.getUtf8(this.descriptorIndex)
}
View Code

   

  再下麵的都是屬性表相關的內容(包括八個屬性表):

  attribute_info.go:屬性表對應的頂層介面,所有的屬性表必須實現該介面

package classfile

//attribute_info {
//u2 attribute_name_index;
//u4 attribute_length;
//u1 info[attribute_length];
//}
//各個屬性表表達的屬性都不相同,所以不能用常量池中表的類型可以靠tag來區分
//這裡是使用屬性名來區分
//並且屬性表中也沒有存實際的數據,存的是指向常量池中ConstantUtf8Info表的索引
type AttributeInfo interface {
    readInfo(reader *ClassReader)
}

//這裡的話獲取class文件中屬性表的數量,根據屬性表的數量去常量池中讀取屬性表
func readAttributes(reader *ClassReader, pool ConstantPool) []AttributeInfo {
    attributesCount := reader.readUint16()
    attributes := make([]AttributeInfo, attributesCount)
    for i := range attributes {
        attributes[i] = readAttribute(reader, pool)
    }
    return attributes
}

//至於怎麼讀屬性表呢?首先讀前兩個位元組表示屬性名稱的索引
// 根據這個索引去常量池中獲取ConstantUtf8Info表中的數據獲取屬性名稱
//然後再讀取4個位元組表示屬性表的長度,根據屬性名稱和長度去讀取各種屬性表的數據,保存到各個結構體中
func readAttribute(reader *ClassReader, pool ConstantPool) AttributeInfo {
    attrNameIndex := reader.readUint16()
    attrName := pool.getUtf8(attrNameIndex)
    attrLength := reader.readUint32()
    attrInfo := newAttributeInfo(attrName, attrLength, pool)
    attrInfo.readInfo(reader)
    return attrInfo
}

func newAttributeInfo(attrName string, attrLen uint32, pool ConstantPool) AttributeInfo {
    switch attrName {
    case "Deprecated":
        return &DeprecatedAttribute{}
    case "Synthetic":
        return &SyntheticAttribute{}
    case "SourceFile":
        
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 員工管理系統 因為學業要求,需要完成一個過關檢測,但是因為檢測之前沒有做好準備,且想到之前用mysql+jdbc+Struts2+bootstrap做成了一個ATM系統(主要有對數據的增刪改查操作),應對這次的檢測應該不成問題,但是萬萬沒想到,過關檢測重在“檢測”,需要在規定的時間內完成一個系統,且 ...
  • Python 程式能用很多方式處理日期和時間,轉換日期格式是一個常見的功能。Python 提供了 time ,datatime, calendar 等模塊可以用於格式化日期和時間。時間間隔是以秒為單位的浮點小數。每個時間戳都以自從 1970 年 1 月 1 日午夜(歷元)經過了多長時間來表示。Pyt ...
  • 本文章將要介紹的內容有以下幾點,讀者朋友也可先自行思考一下相關問題: 1. 線程中斷 interrupt 方法怎麼理解,意思就是線程中斷了嗎?那當前線程還能繼續執行嗎? 2. 判斷線程是否中斷的方法有幾個,它們之間有什麼區別? 3. LockSupport的 park/unpark 和 wait/n ...
  • defer 關鍵字 首先來看官網的定義: A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because ...
  • 1、運算符 運算符 功能 是否支持 字元串 列表 元組 字典 集合 + 合併 √ √ √ * 複製 √ √ √ in 判斷是否存在 √ √ √ √ √ not in 判斷是否不存在 √ √ √ √ √ 2、公共方法 len() 統計容器中元素的個數 del/del() 刪除 max() 返回容器中元 ...
  • 春節期間熟悉了TP6, 也寫了一個TP6的博客程式,但系統的異常頁面實在另外頭疼,很多時候無法查看到是哪行代碼出的問題。 所以就特別的想把whoops引進來,經過一系列的研究,終於找到瞭解決的辦法: 1. 通過composer安裝whoops 運行命令: composer require filp/ ...
  • 目錄 1. 隱式類型轉換 2. 顯示類型轉換/強制類型轉換( static_cast、const_cast、reinterpret_cast、dynamic_cast) 3. 類型轉換函數、轉換構造函數 類型轉換可分為 隱式類型轉換(編譯器自動完成) 與 顯示類型轉換(強制類型轉換,需要自己操作)。 ...
  • 先用一個數組表示一個二叉樹搜索樹,也就是一個排好序的二叉樹,其中左子結點<根結點<右子結點 利用結構數組的形式來表示,id , left , right 代表結點id ,左子樹 ,右子樹 下麵這個二維數組 $data[]=['id'=>8,'left'=>2,'right'=>10,'data'=> ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...