欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go reflect 反射原理示例詳解

 更新時間:2022年11月02日 10:48:37   作者:飛天薯?xiàng)l  
這篇文章主要為大家介紹了Go reflect 反射原理示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

開始之前

在開始分析原理之前,有必要問一下自己一個問題:

反射是什么?以及其作用是什么?

不論在哪種語言中,我們所提到的反射功能,均指開發(fā)者可以在運(yùn)行時通過調(diào)用反射庫來獲取到來獲取到指定對象類型信息,通常類型信息中會包含對象的字段/方法等信息。并且,反射庫通常會提供方法的調(diào)用, 以及字段賦值等功能。

使用反射可以幫助我們避免寫大量重復(fù)的代碼, 因此反射功能常見用于ORM框架, 以及序列化何反序列化框架,除此之外在Java中反射還被應(yīng)用到了AOP等功能中。

了解完反射的功能之后,我們再引申一個問題:

假如你開發(fā)了一種語言, 該如何為開發(fā)者提供反射的功能?

首先,我們知道反射的核心的功能有:

  • 類型信息獲取
  • 對象字段訪問/賦值
  • 方法調(diào)用

因此實(shí)際作為語言的開發(fā)者(假設(shè)),我們要解決的問題有:

  • 如何存儲并獲取到對象類型信息?
  • 如何定位到對象字段的內(nèi)存地址?

注: 只要知道了對象字段的內(nèi)存地址配合上類型信息,我們便可以實(shí)現(xiàn)賦值與訪問的操作。

  • 如何定位到方法的內(nèi)存地址?

注:代碼在內(nèi)存中也是數(shù)據(jù),因此只需要定位到代碼所在的地址,便可解決方法調(diào)用的問題

分析

從何處獲取類型信息

如果你熟悉Go的reflect(反射)庫, 相信你或多或少的聽過反射三原則, 即:

  • interface{}可以反射出反射對象
  • 從反射對象中可以獲取到interface{}
  • 要修改反射對象, 其值必須可設(shè)置

根據(jù)以上三原則不難看出interface{}是實(shí)現(xiàn)反射功能的基石, 那么這是為什么呢?

要回答這個問題,我們了解interface{}的本質(zhì)是什么。

interface{}本質(zhì)上Go提供的一種數(shù)據(jù)類型, 與其他數(shù)據(jù)類型不同的是, interface{}會為我們提供變量的類型信息以及變量所在的內(nèi)存地址。

Runtime中使用結(jié)構(gòu)體來表示interface{}, 其結(jié)構(gòu)如下所示:

type emptyInterface struct {
    typ  *rtype
    word unsafe.Pointer  
}

該結(jié)構(gòu)體只有兩個字段, 分別是:

  • typ 變量的類型信息, 這一步驟在編譯步驟便可確定下來
  • word 指向變量數(shù)據(jù)的指針, 這一步驟在運(yùn)行時進(jìn)行確定

接下來我們通過反編譯下文的代碼, 來觀察當(dāng)把一個變量轉(zhuǎn)換成interface{}的時候都發(fā)生了什么:

package main
import "fmt"
func main() {
	s := 1024
	var a interface{} = &s
	fmt.Println(a)
}

執(zhí)行以下命令, 獲取匯編代碼

go tool compile -N -S .\main.go

以下代碼即為將字符串賦值給interface{}類型的變量a的對應(yīng)匯編代碼

0x0057 00087 (.\main.go:7)      MOVQ    "".&s+104(SP), AX
0x005c 00092 (.\main.go:7)      MOVQ    AX, ""..autotmp_9+88(SP)
0x0061 00097 (.\main.go:7)      LEAQ    type.*int(SB), CX
0x0068 00104 (.\main.go:7)      MOVQ    CX, "".a+144(SP)
0x0070 00112 (.\main.go:7)      MOVQ    AX, "".a+152(SP)

相信即便你不熟悉匯編,但至少也發(fā)現(xiàn)了, 以上代碼做了如下操作:

  • 獲取變量s的地址, 保存到AX寄存器, 并往a+144的地址寫入數(shù)據(jù)
  • 獲取變量s的類型信息(type.*int),保存到CX寄存器, 并往a+152的地址寫入數(shù)據(jù)

注:感興趣的讀者可以把取地址的操作去掉,再看看有什么不同

此外, 我們還可以通過指針數(shù)據(jù)類型轉(zhuǎn)換來獲取到interface{}中的數(shù)據(jù)來側(cè)面驗(yàn)證一下。

注: unsafe.Pointer 可以轉(zhuǎn)換成任意類型的指針

type EmptyInterface struct {
	typ  unsafe.Pointer
	word unsafe.Pointer
}
func getWordPtr(i interface{})  unsafe.Pointer {
	eface := *(*EmptyInterface)(unsafe.Pointer(&i))
	return eface.word
}
func Test_GetWordPtr(t *testing.T) {
	str := "Hello, KeSan"
	strPtr := &str
	//此處由編譯器做了類型轉(zhuǎn)換 *string -> interface{}
	wordPtr := getWordPtr(strPtr)
	t.Logf("String Ptr: %p",  strPtr)
	t.Logf("Word Ptr: %p", wordPtr)
}

輸入如下所示:

因此,不難推出reflect.TypeOf的實(shí)現(xiàn)實(shí)際上就是獲取interface{}type信息,并返回給開發(fā)人員。其代碼如下所示:

func TypeOf(i interface{}) Type {
	eface := *(*emptyInterface)(unsafe.Pointer(&i))
	return toType(eface.typ)
}
// 將 *rtype 轉(zhuǎn)成接口類型的Type
func toType(t *rtype) Type {
	if t == nil {
		return nil
	}
	return t
}

再進(jìn)一步我們可以來看看類型信息中都包含了什么?

結(jié)構(gòu)體rtype描述了基礎(chǔ)的類型信息,其字段如下所示:

type rtype struct {
	size       uintptr
	ptrdata    uintptr // number of bytes in the type that can contain pointers
	hash       uint32  // hash of type; avoids computation in hash tables
	tflag      tflag   // extra type information flags
	align      uint8   // alignment of variable with this type
	fieldAlign uint8   // alignment of struct field with this type
	kind       uint8   // enumeration for C
	// function for comparing objects of this type
	// (ptr to object A, ptr to object B) -> ==?
	equal     func(unsafe.Pointer, unsafe.Pointer) bool
	gcdata    *byte   // garbage collection data
	str       nameOff // string form
	ptrToThis typeOff // type for pointer to this type, may be zero
}

rtype結(jié)構(gòu)體包含了Golang中所有數(shù)據(jù)類型的基礎(chǔ)類型信息, 對于不同的數(shù)據(jù)類型其類型信息會有略微的差異。

// 結(jié)構(gòu)體的類型信息
type structType struct {
	rtype
	pkgPath name
	fields  []structField // sorted by offset
}
// channel 的類型信息
type chanType struct {
	rtype
	elem *rtype  // channel element type
	dir  uintptr // channel direction (ChanDir)
}

如何實(shí)現(xiàn)賦值操作?

賦值操作的本質(zhì)上是往對應(yīng)的內(nèi)存地址寫入數(shù)據(jù), 因此我們有必要簡單了解一下結(jié)構(gòu)體在內(nèi)存中的布局方式, 以一個最為簡單坐標(biāo)的結(jié)構(gòu)體為例,其結(jié)構(gòu)體如下所示:

type Coordinate struct {
    X int64
    Y int64
    Z int64
}

其在內(nèi)存中的表現(xiàn)為一段大小為24字節(jié)的連續(xù)內(nèi)存,具體如下圖所示

因此,我們實(shí)際上要做的就是獲取到結(jié)構(gòu)體的首地址之后,根據(jù)各個字段相對首字段的偏移地址計(jì)算出其在內(nèi)存中地址。

實(shí)際上在Runtime提供的類型信息中,已經(jīng)包含了各個字段的偏移以及類型信息,我們可以具體的來看一下反射功能獲取字段Field的實(shí)現(xiàn)。

func (v Value) Field(i int) Value {
	if v.kind() != Struct {
		panic(&ValueError{"reflect.Value.Field", v.kind()})
	}
	// 獲取類型信息
	tt := (*structType)(unsafe.Pointer(v.typ))
	if uint(i) >= uint(len(tt.fields)) {
		panic("reflect: Field index out of range")
	}
	// 獲取字段信息
	field := &tt.fields[i]
	typ := field.typ
	// 繼承結(jié)構(gòu)體的部分flag信息
	fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind())
	if !field.name.isExported() {
		if field.embedded() {
			fl |= flagEmbedRO
		} else {
			fl |= flagStickyRO
		}
	}
	// 根據(jù)偏移地址計(jì) + 結(jié)構(gòu)體的首地址 計(jì)算出 字段在內(nèi)存中的地址, 并返回Value對象
	ptr := add(v.ptr, field.offset(), "same as non-reflect &v.field")
	return Value{typ, ptr, fl}
}

了解到如何獲取字段在內(nèi)存中的地址之后,我們再來看看賦值操作是如何實(shí)現(xiàn)。

如以下代碼SetInt所示, 本質(zhì)上還是一些指針的轉(zhuǎn)換以及解引用。

func (v Value) SetInt(x int64) {
	v.mustBeAssignable()
	switch k := v.kind(); k {
	default:
		panic(&ValueError{"reflect.Value.SetInt", v.kind()})
	case Int:
		*(*int)(v.ptr) = int(x)
	case Int8:
		*(*int8)(v.ptr) = int8(x)
	case Int16:
		*(*int16)(v.ptr) = int16(x)
	case Int32:
		*(*int32)(v.ptr) = int32(x)
	case Int64:
		*(*int64)(v.ptr) = x
	}
}

那么,肯定有同學(xué)會問,為啥你一直都在講結(jié)構(gòu)體啊,那字符串(string), 切片(slice), map呢?

實(shí)際上這些Go的內(nèi)建的數(shù)據(jù)類型,在Runtime中的表現(xiàn)形式也是結(jié)構(gòu)體, 我們可以在reflect包中找到如下定義:

// 切片頭
type SliceHeader struct {
	Data uintptr // 數(shù)組的指針地址
	Len  int     // 數(shù)組長度
	Cap  int     // 數(shù)組容量
}
// 字符串頭
type StringHeader struct {
	Data uintptr // 字節(jié)數(shù)組的指針地址
	Len  int     // 字節(jié)數(shù)組的長度
}

因此,通過反射來操作切片和字符串本質(zhì)上還是操作結(jié)構(gòu)體。

總結(jié)

  • interface{}是一種數(shù)據(jù)類型, 其存儲了變量的類型信息與數(shù)據(jù)指針,其中類型信息是在編譯期間確定下來的
  • Golang反射的原理就是從interface{}中獲取到類型信息以及變量的指針,從而實(shí)現(xiàn)類型獲取以及賦值的功能

以上就是Go reflect 反射原理示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Go reflect 反射原理的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go語言中三種不同md5計(jì)算方式的性能比較

    Go語言中三種不同md5計(jì)算方式的性能比較

    md5計(jì)算在我們?nèi)粘9ぷ鞯臅r候經(jīng)常能遇到,下面這篇文章主要介紹了Go語言中三種不同md5計(jì)算方式的性能比較,需要的朋友可以參考借鑒,下面來一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-01-01
  • golang實(shí)現(xiàn)redis的延時消息隊(duì)列功能示例

    golang實(shí)現(xiàn)redis的延時消息隊(duì)列功能示例

    這篇文章主要介紹了golang實(shí)現(xiàn)redis的延時消息隊(duì)列功能,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • Go語言應(yīng)該什么情況使用指針

    Go語言應(yīng)該什么情況使用指針

    go語言的指針類型和C/C++的指針類型用法是一樣的,那么Go語言應(yīng)該什么情況使用指針,本文就詳細(xì)的介紹一下,感興趣的可以了解一下
    2021-07-07
  • 使用go自定義prometheus的exporter

    使用go自定義prometheus的exporter

    在prometheus中如果要監(jiān)控服務(wù)器和應(yīng)用的各種指標(biāo),需要用各種各樣的exporter服務(wù),這篇文章主要介紹了使用go自定義prometheus的exporter,需要的朋友可以參考下
    2023-03-03
  • 詳解Golang中下劃線的使用方法

    詳解Golang中下劃線的使用方法

    這篇文章主要介紹了詳解Golang中下劃線的使用方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-01-01
  • 手把手教你用VS?code快速搭建一個Golang項(xiàng)目

    手把手教你用VS?code快速搭建一個Golang項(xiàng)目

    Go語言是采用UTF8編碼的,理論上使用任何文本編輯器都能做Go語言開發(fā),下面這篇文章主要給大家介紹了關(guān)于使用VS?code快速搭建一個Golang項(xiàng)目的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-04-04
  • golang為什么要統(tǒng)一錯誤處理

    golang為什么要統(tǒng)一錯誤處理

    這篇文章主要介紹了golang為什么要統(tǒng)一錯誤處理,統(tǒng)一錯誤處理的目的是為了前端開發(fā)接收到后端的statuscode,之后便于前端邏輯上開發(fā)以及開發(fā),下文具體操作過程需要的小伙伴可以參考一下
    2022-04-04
  • sublime text3解決Gosublime無法自動補(bǔ)全代碼的問題

    sublime text3解決Gosublime無法自動補(bǔ)全代碼的問題

    本文主要介紹了sublime text3解決Gosublime無法自動補(bǔ)全代碼的問題,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • Go語言CSP并發(fā)模型實(shí)現(xiàn)MPG

    Go語言CSP并發(fā)模型實(shí)現(xiàn)MPG

    這篇文章主要為大家介紹了Go語言CSP并發(fā)模型實(shí)現(xiàn)MPG圖文詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • 深入解析Golang中JSON的編碼與解碼

    深入解析Golang中JSON的編碼與解碼

    隨著互聯(lián)網(wǎng)的快速發(fā)展和數(shù)據(jù)交換的廣泛應(yīng)用,各種數(shù)據(jù)格式的處理成為軟件開發(fā)中的關(guān)鍵問題,本文將介紹?Golang?中?JSON?編碼與解碼的相關(guān)知識,幫助大家了解其基本原理和高效應(yīng)用,需要的可以收藏一下
    2023-05-05

最新評論