golang reflect 實現原理
golang reflect 實現原理
本文主要講述 reflect 庫實現的原理思路,reflect 包實現具有兩個基礎 unsafe 操作記憶體對齊和 runtime 包的變數。
runtime 變數
runtime 變數是 reflect 的實現基礎,基於 unsafe 包操作 runtime 變數實現 reflect 功能。
首先我們按照 go 的規則先簡單的定義一個變數型別 Value,Value 有兩個型別成員屬性 typ 和 ptr,typ 是型別表示這個變數是什麼物件,ptr 是一個地址指向這個變數的地址。
// 如果看reflect或runtime原始碼會發現兩者相識,只不過被我刪了不少屬性。
type Value struct {
typ Type
ptr uintptr
}
type Type interface {
Name() string // by all type
Index(int) Value // by Slice Array
MapIndex(value) Value // by Map
Send(Value) // By Chan
}
當我們去操作一個變數時就按照 Type 型別來操作,而操作物件的資料就在記憶體的 ptr 位置。
變數型別 Type 定義的是一個介面,因為不同型別有不同的操作方法,例如 Map 的獲取/設定值,Slice 和 Array 的獲取一個索引,Chan 具有傳送和接實一個物件,Struct 可以獲得一個結構體屬性,屬性具有 tag,這樣不同的型別就具有不同獨特的操作方法,如果 Map 型別呼叫 Index 方法無法實現就會 panic 了。
理解變數本質就是一個資料地址和一個型別資料組成,然後基於者兩個變數來操作就是 reflect。
reflect example
一個 reflect 簡單的例子,reflect.TypeOf
和reflect.ValueOf
方法將一個 runtime 型別和變數轉換成 reflect 型別和變數,依賴 unsafe 操作記憶體對齊來強制轉換,reflect 型別和變數和 runtime 中一樣的,就可以實現自由操作了。
最後reflect.Value
呼叫Interface()
方法將變數從 reflect 狀態轉換回來成 runtime 狀態了。
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
Age int
}
func main() {
s := new(Student)
fmt.Println(reflect.TypeOf(s))
fmt.Println(reflect.TypeOf(s).Elem())
fmt.Println(reflect.TypeOf(*s))
v := reflect.ValueOf(s).Elem()
v.Field(0).SetString("66")
fmt.Printf("%#v\n", v.Interface())
}
reflect Type
先從 reflect/type.go 簡單的抄一點程式碼來。rtype 物件就是 Type 介面的簡化實現,kind 就是這個型別的型別,然後其他組合型別 (Ptr、Slice、Map 等) 就額外新增了一些屬性和方法。
type rtype struct {
size uintptr
ptrdata uintptr
kind uint8
...
}
ptrType 是指標型別的定義,屬性 rtype 就是指標的型別,elem 就是指標指向的型別,那麼一個 Ptr Type 呼叫 Elem 獲得指標的型別就返回了 elem 值。
// ptrType represents a pointer type.
type ptrType struct {
rtype
elem *rtype // pointer element (pointed at) type
}
structType 是指標型別的定義,rtype 是結構體型別的基礎資訊,pkgPath 就是結構體的名稱,當一個結構體呼叫 Name 方法時就返回了 pkgPath,如果是結構體指標呼叫 Name 方法就沒有返回資料,因為沒有 pkgPath 需要先 Elem 一次轉換成結構體型別,而結構體型別的 Field、FieldByIndex、FieldByName、FieldByNameFunc 方法就物件結構體型別 fields 資訊進行變數操作了。
而在結構體屬性 structField 中,name、typ 分別記錄這個屬性的名稱和型別,offsetEmbed 是屬性偏移位置。
// structType represents a struct type.
type structType struct {
rtype
pkgPath name
fields []structField // sorted by offset
}
// Struct field
type structField struct {
name name // name is always non-empty
typ *rtype // type of field
offsetEmbed uintptr // byte offset of field<<1 | isEmbedded
}
chanType 是 chan 型別的 ing 有,rtype 是 chan 本身,elem 是 chan 操作物件的型別和指標指向相識,dir 就是 chan 的反向進、出、進出。
// chanType represents a channel type.
type chanType struct {
rtype
elem *rtype // channel element type
dir uintptr // channel direction (ChanDir)
}
sliceType 是切片型別定義,切片型別 rtype 是本身資訊,elem 就是切片操作的物件型別。
// sliceType represents a slice type.
type sliceType struct {
rtype
elem *rtype // slice element type
}
arrayType 是陣列型別,在切片上額外多了兩個屬性,slice 是陣列轉換成切片的型別,預先靜態定義好了,而 len 是陣列長度。
// arrayType represents a fixed array type.
type arrayType struct {
rtype
elem *rtype // array element type
slice *rtype // slice type
len uintptr
}
上述 example 講述了部分型別的定義,完整檢視原始碼 reflect.type.go。
method、interface、map 暫未完全看完,懂原理後沒必要看沒有幾行使用相關知識。
reflect Kind
reflect.Kind 是定義反射型別常量,是型別的標識。rtype 的 kind 屬性就是指 reflect.Kind。
// A Kind represents the specific kind of type that a Type represents.
// The zero Kind is not a valid kind.
type Kind uint
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
reflect Type method
Kind 方法註釋說明返回 kind 值就是 rtype.kind,型別是 reflect.Kind 是 go 中型別的主要分類,是 iota 定義的型別常量。
// Kind returns the specific kind of this type.
Kind() Kind
變數實現的方法定義在型別連續後面的一塊記憶體中,可以 unsafe 讀到一個型別的全部方法,就可以實現 Implements 方法判斷是否實現了一個介面了。
// Implements reports whether the type implements the interface type u.
Implements(u Type) bool
ChanDir 方法很簡單就返回 chanType.dir,註釋說如果不是 Chan 型別 panic 了,型別不 chan 就沒有 dir 這個屬性無法處理就 panic 了,在呼叫前一般都明確了 Kind 是 Chan。
// ChanDir returns a channel type's direction.
// It panics if the type's Kind is not Chan.
ChanDir() ChanDir
Elem 方法全稱是 element,就是指元素型別也可以叫指向型別,註釋要求 Kind 必須是 Array、Chan、Map、Ptr、Slice 型別否在就 panic,和 Chan 的 ChanDir 方法一樣,只有這 5 個型別才有 elem 屬性。
檢視前面定義就可以知道 Arry、Slice、Ptr、Chan 的 elem 就是指向的物件的型別,map 是值的型別,例如以下型別 Elem 後 Kind 都是 Int。
[20]int
[]int
*int
chan int
map[string]int
// Elem returns a type's element type.
// It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.
Elem() Type
Field 和 NumField 方法是獲得結構體的指定索引的屬性和結構體屬性數量,註釋一樣有說明要求 Kind 是 Struct 型別否在 panic,因為就結構體型別才有 [] StructField 能實現這些方法。
根據前面 structType 定義兩個方法的實現思路就是 typ.fields[i] 轉換一下和 len(typ.fields).
// Field returns a struct type's i'th field.
// It panics if the type's Kind is not Struct.
// It panics if i is not in the range [0, NumField()).
Field(i int) StructField
// NumField returns a struct type's field count.
// It panics if the type's Kind is not Struct.
NumField() int
NumIn 和 In 方法是 Func Kind 獨有的方法,NumIn 返回這個 Func 具有多個入參,對於返回引數就是 NumOut;In 方法是獲得這個 Func 指定第 i 引數的型別。
// NumIn returns a function type's input parameter count.
// It panics if the type's Kind is not Func.
NumIn() int
// In returns the type of a function type's i'th input parameter.
// It panics if the type's Kind is not Func.
// It panics if i is not in the range [0, NumIn()).
In(i int) Type
Key 方法是 Map Kind 獨有方法,返回 map 鍵的型別。
// Key returns a map type's key type.
// It panics if the type's Kind is not Map.
Key() Type
Len 方法是 Array Kind 獨有方法,返回 Array 定義的長度。
// Len returns an array type's length.
// It panics if the type's Kind is not Array.
Len() int
上述說明 reflect.Type 的部分方法實現原理,剩餘方法原理類似,就是操作 rtype 的屬性,部分 Kind 型別是具有獨有方法可以呼叫。
reflect.Value Method
反射 Value 物件定義了三個屬性 型別、資料位置、flag,資料記憶體位置就在 ptr 位置,操作方法就需要依靠 typ 型別來判斷資料型別操作了。
Type 是靜態資料,而 Value 是動態資料,Value 的很多方法具體值是和資料相關的。
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}
通用方法
通用方法是指所有型別具有的方法,僅說明根據 Type 和 Value 定義實現這個方法大概的思路,具體實現程式碼並不一樣,以原始碼為準。
Type 方法返回這個值的型別,大致思路就是返回 v.typ,具體實現還有一些額外處理。
func (v Value) Type() Type
Kind 方法實現大致思路就是返回 v.typ.kind。
// Kind returns v's Kind. If v is the zero Value (IsValid returns false),
// Kind returns Invalid.
func (v Value) Kind() Kind
Interface 法思路就是返回 v.ptr 值轉換成一個 interface{}變數,這樣就從 reflect.Value 重新轉換會變數了。
// Interface returns v's current value as an interface{}.
// It is equivalent to:
// var i interface{} = (v's underlying value)
// It panics if the Value was obtained by accessing unexported struct fields.
func (v Value) Interface() (i interface{})
Convert 方法思路就是 v.ptr 值轉換成引數 t 的型別,實現規則是Conversions 語法文件 映象地址。
// Convert returns the value v converted to type t. If the usual Go conversion rules do not allow conversion of the value v to type t, Convert panics.
func (v Value) Convert(t Type) Value
Set 方法實現就是設定 v.ptr=x.ptr,要求 v 和 x 的型別是一樣的。
同時要這個 Value 是 CanSet,如果將一個 int 轉換成 reflect.Value,函式傳遞的是一個值的副本,那麼再對 int 設定新的值就無效了,CanSet 返回就是 false,需要傳遞*int 這樣的指標型別才能有效設定
// Set assigns x to the value v. It panics if CanSet returns false. As in Go, x's value must be assignable to v's type.
func (v Value) Set(x Value)
SetBool 方法是設定 bool Kind 的值,前置要求 Kind 是一樣的,型別還有 SetInt、SetString 等方法。
// SetBool sets v's underlying value. It panics if v's Kind is not Bool or if CanSet() is false.
func (v Value) SetBool(x bool)
Method 返回這個值的指定索引方法。
// Method returns a function value corresponding to v's i'th method.
// The arguments to a Call on the returned function should not include
// a receiver; the returned function will always use v as the receiver.
// Method panics if i is out of range or if v is a nil interface value.
func (v Value) Method(i int) Value
獨有方法
Len 方法返回資料資料,註釋說明要求是 Array, Chan, Map, Slice, or String,前四個返回就是資料量,而 String Kind 返回字串長度。
// It panics if v's Kind is not Array, Chan, Map, Slice, or String.
func (v Value) Len() int
IsNil 方法判斷指標是否是空,在 go 的實現中 chan、func、interface、map、pointer、slice 底層才是指標型別,才能判斷 IsNil 否在 panic,判斷這些指標型別的 ptr 是否為 0,在 go 程式碼編寫中也只有這幾種型別可以i==nil
這樣的比較。
在 go1.13 中新增了 IsZero 方法,判斷是否是空值,裡面這些指標型別會判斷 IsNil,其他型別就是判斷資料值是不是零值那樣。
// IsNil reports whether its argument v is nil. The argument must be
// a chan, func, interface, map, pointer, or slice value; if it is
// not, IsNil panics. Note that IsNil is not always equivalent to a
// regular comparison with nil in Go. For example, if v was created
// by calling ValueOf with an uninitialized interface variable i,
// i==nil will be true but v.IsNil will panic as v will be the zero Value.
func (v Value) IsNil() bool
Index 方法獲取指定型別的索引,就 Array、Slice、String 可以執行,否在 panic,在 ptr 指向的位置進行一個計算得到的偏移位置獲得到索引的值。
// Index returns v's i'th element. It panics if v's Kind is not Array, Slice, or String or i is out of range.
func (v Value) Index(i int) Value
Field 方法是返回結構體指定索引的值,要求 Kind 是 Struct,通過指定索引的偏移來獲得這個值的地址,然後型別裡面獲得到型別,最後返回索引的值。
// Field returns the i'th field of the struct v. It panics if v's Kind is not Struct or i is out of range.
func (v Value) Field(i int) Value
Elem 方法是返回 Ptr 和 Interface Kind 指向值,為了解除引用。
為什麼 Value.Elem 方法沒有了 Slice、Map 等型別? 具體額外獨立的操作方法 Index、MapIndex 等。
// Elem returns the value that the interface v contains or that the pointer v points to.
// It panics if v's Kind is not Interface or Ptr. It returns the zero Value if v is nil.
func (v Value) Elem() Value
MapIndex 和 MapKeys 是 Map Kind 獨有的方法,獲取到 map 的索引值和全部鍵,通過 typ 提供的型別和 ptr 地址進行復雜的 map 操作。
// MapIndex returns the value associated with key in the map v.
// It panics if v's Kind is not Map.
// It returns the zero Value if key is not found in the map or if v represents a nil map.
// As in Go, the key's value must be assignable to the map's key type.
func (v Value) MapIndex(key Value) Value
// MapKeys returns a slice containing all the keys present in the map,
// in unspecified order.
// It panics if v's Kind is not Map.
// It returns an empty slice if v represents a nil map.
func (v Value) MapKeys() []Value
Send 方法是 Chan Kind 獨有方法,給 chan 放一個資料進去。
func (v Value) Send(x Value)
end
以上講述了 reflect 庫的原理就是操作 runtime 變數,而 runtime 變數就是一個型別加地址。
本文並沒有完整分析 reflect 庫,通過這些原理就可以大概理解這些方法的作用和操作了,具體請參考原始碼。
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- golang reflect 常見示例Golang
- golang實現常用集合原理介紹Golang
- 【Go】Golang實現gRPC的Proxy的原理GolangRPC
- 深入理解Golang之interface和reflectGolang
- Golang make和new的區別及實現原理詳解Golang
- ElasticSearch IK熱詞自動熱更新原理與Golang實現ElasticsearchGolang
- 【GoLang 那點事】深入 Go 的 Map 使用和實現原理Golang
- 巧用 -webkit-box-reflect 倒影實現各類動效WebKit
- 【Vue3響應式原理#02】Proxy and ReflectVue
- 深度剖析Reflect + 實戰案例
- Golang 心跳的實現Golang
- Reflect
- golang如何實現單例Golang單例
- 計數排序 -- GoLang實現排序Golang
- golang實現稀疏陣列Golang陣列
- Golang實現ForkJoin小文Golang
- 利用 Watermill 實現 Golang CQRSGolang
- golang實現單例模式Golang單例模式
- Golang map執行緒安全實現及sync.map使用及原理解析。Golang執行緒
- Golang 學習——如何判斷 Golang 介面是否實現?Golang
- Golang 實現 Redis(5): 使用跳錶實現 SortedSetGolangRedis
- AQS實現原理AQS
- Condition實現原理
- Synchronized 實現原理synchronized
- LinkedList實現原理
- block實現原理BloC
- ReentrantLock實現原理ReentrantLock
- synchronized實現原理synchronized
- AsyncTask實現原理
- jQuery實現原理jQuery
- AOP如何實現及實現原理
- Golang可重入鎖的實現Golang
- golang實現子程式通訊Golang
- golang 進度條功能實現Golang
- Golang協程池(workpool)實現Golang
- Golang實現PHP常用函式GolangPHP函式
- golang洗牌演算法實現Golang演算法
- Golang實現氣泡排序法Golang排序