Go語言中的反射原理解析與應(yīng)用
引言
反射(Reflection)是計算機(jī)科學(xué)中的一個重要概念,它允許程序在運(yùn)行時檢查變量和值,獲取它們的類型信息,并且能夠修改它們。
Go語言通過內(nèi)置的reflect包提供了反射功能,使得開發(fā)者可以編寫靈活的代碼,處理各種不同類型的值,而不必在編譯時就知道這些值的具體類型。
本文將結(jié)合實(shí)際案例,詳細(xì)介紹Go語言中反射的基本概念、關(guān)鍵函數(shù)以及使用場景。
一、反射的基本概念
在Go語言中,反射允許程序在運(yùn)行時動態(tài)獲取變量的各種信息,比如變量的類型、值等。
如果變量是結(jié)構(gòu)體類型,還可以獲取到結(jié)構(gòu)體本身的各種信息,比如結(jié)構(gòu)體的字段、方法。通過反射,還可以修改變量的值、調(diào)用方法。使用反射需要引入reflect包。
Go語言中的每一個變量都包含兩部分信息:類型(type)和值(value)。reflect包讓我們能夠在運(yùn)行時獲取這些信息。
reflect.TypeOf()函數(shù)用于獲取任何值的類型,返回一個reflect.Type類型的值。
reflect.ValueOf()函數(shù)用于獲取任何值的運(yùn)行時表示,返回一個reflect.Value類型的值。
二、靜態(tài)類型與動態(tài)類型
Go語言是靜態(tài)類型的語言,但是也可以通過反射機(jī)制實(shí)現(xiàn)一些動態(tài)語言的功能
在反射過程中,編譯的時候就知道變量類型的就是靜態(tài)類型、如果在運(yùn)行時候才知道類型的就是動態(tài)類型
靜態(tài)類型:變量在聲明時候給他賦予類型的
var name string // string是靜態(tài)類型的 var age int // int 是靜態(tài)類型的
動態(tài)類型:在運(yùn)行的時候可能發(fā)生變化,主要考慮賦值問題
var A interface{} // interface{} 靜態(tài)類型 A = 10 // interface{} 靜態(tài)類型 此時的A是動態(tài)類型 int A = "jingtian" // interface{} 靜態(tài)類型 此時的A是動態(tài)類型 string
像js,pyhton都是動態(tài)類型語言,定義變量的時候,不用指明類型,給它什么類型,運(yùn)行時就是什么類型
三、為什么要用反射
1、我們需要編寫一個函數(shù),但是不知道函數(shù)傳遞給我的參數(shù)時什么?沒約定好,傳入的類型太多,這些類型不能統(tǒng)一表示,反射
2、我們在某些使用,需要根據(jù)條件來判斷具體使用哪個函數(shù)處理問題,根據(jù)用戶的輸入來決定,這時候就需要對函數(shù)的參數(shù)進(jìn)行反射,在運(yùn)行期間來動態(tài)處理。
3、開發(fā)一些腳手架,框架的時候,自動實(shí)現(xiàn)一些底層的判斷。比如 interface{} = any,由于這種動態(tài)類型是不確定的,我們可能在底層代碼進(jìn)行判斷,從而選擇使用什么來處理。
4、反射主要就是在不確定數(shù)據(jù)類型的和值的時候使用。如果我們不知道這個對象的信息,我們可以通過這個對象拿到代碼中的一切。
四、為什么不建議使用反射
1、和反射相關(guān)的代碼,不方便閱讀,開發(fā)中,代碼可讀性(指標(biāo))很重要
2、Go語言是靜態(tài)類型的語言,編譯器可以找出開發(fā)時候的錯誤,如果代碼中有大量反射代碼,隨時可能存在安全問題,panic,項目就終止
3、反射的性能很低,相對于正常的開發(fā),至少慢2-3個數(shù)量級。項目關(guān)鍵位置低耗時,一定是不能使用反射的。更多時候使用約定。
五、反射的關(guān)鍵函數(shù)
1. reflect.Type 類型
reflect.Type
是一個接口,通過reflect.TypeOf
函數(shù)對接收的任意數(shù)據(jù)類型進(jìn)行反射,可以獲取該類型的各種信息。reflect.Type
接口包含以下方法:
Kind()
:返回該接口的具體分類(Kind)。Name()
:返回該類型在自身包內(nèi)的類型名,如果是未命名類型會返回空字符串。PkgPath()
:返回類型的包路徑,即明確指定包的import路徑。對于內(nèi)建類型(如string、error)或未命名類型(如*T、struct{}、[]int),會返回空字符串。String()
:返回類型的字符串表示。Size()
:返回要保存一個該類型的值需要多少字節(jié)。Align()
:返回當(dāng)從內(nèi)存中申請一個該類型值時,會對齊的字節(jié)數(shù)。FieldAlign()
:返回當(dāng)該類型作為結(jié)構(gòu)體的字段時,會對齊的字節(jié)數(shù)。Implements(u Type)
:如果該類型實(shí)現(xiàn)了u
代表的接口,返回真。AssignableTo(u Type)
:如果該類型的值可以直接賦值給u
代表的類型,返回真。ConvertibleTo(u Type)
:如果該類型的值可以轉(zhuǎn)換為u
代表的類型,返回真。Bits()
:返回該類型的位字?jǐn)?shù)。如果該類型的Kind不是Int、Uint、Float或Complex,會panic。Len()
:返回array類型的長度,如非數(shù)組類型將panic。Elem()
:返回該類型的元素類型,如果該類型的Kind不是Array、Chan、Map、Ptr或Slice,會panic。Key()
:返回map類型的鍵的類型,如非映射類型將panic。ChanDir()
:返回一個channel類型的方向,如非通道類型將會panic。NumField()
:返回struct類型的字段數(shù)(匿名字段算作一個字段),如非結(jié)構(gòu)體類型將panic。Field(i int)
:返回struct類型的第i個字段的類型,如非結(jié)構(gòu)體或者i不在[0, NumField())內(nèi)將會panic。FieldByIndex(index []int)
:返回索引序列指定的嵌套字段的類型,等價于用索引中每個值鏈?zhǔn)秸{(diào)用本方法,如非結(jié)構(gòu)體將會panic。FieldByName(name string)
:返回該類型名為name的字段(會查找匿名字段及其子字段),布爾值說明是否找到,如非結(jié)構(gòu)體將panic。FieldByNameFunc(match func(string) bool)
:返回該類型第一個字段名滿足函數(shù)match的字段,布爾值說明是否找到,如非結(jié)構(gòu)體將會panic。IsVariadic()
:如果函數(shù)類型的最后一個輸入?yún)?shù)是"…"形式的參數(shù),返回真。NumIn()
:返回func類型的參數(shù)個數(shù),如果不是函數(shù),將會panic。
2. reflect.Value 類型
reflect.Value
是一個結(jié)構(gòu)體,為Go值提供了反射接口。reflect.Value
包含以下方法:
IsValid()
:判斷Value是否包含有效的值。IsNil()
:判斷Value是否為nil。Kind()
:返回Value的Kind。Type()
:返回Value的類型。Convert(t Type)
:將Value轉(zhuǎn)換為Type類型的值。Elem()
:如果Value是一個指針、數(shù)組、切片、映射、通道或接口,返回它指向或持有的元素。Bool()
、Int()
、Uint()
、Float()
、Complex()
:分別返回Value的布爾、整數(shù)、無符號整數(shù)、浮點(diǎn)數(shù)和復(fù)數(shù)表示。OverflowInt(x int64)
、OverflowUint(x uint64)
、OverflowFloat(x float64)
、OverflowComplex(x complex128)
:分別判斷將Value轉(zhuǎn)換為整數(shù)、無符號整數(shù)、浮點(diǎn)數(shù)和復(fù)數(shù)時是否會溢出。Bytes()
:返回Value的字節(jié)切片表示。String()
:返回Value的字符串表示。Pointer()
:返回Value的指針表示。InterfaceData()
:返回Value的接口數(shù)據(jù),用于類型斷言。Slice(i, j int)
:返回Value的切片,從索引i到索引j(不包括j)。Slice3(i, j, k int)
:返回Value的三維切片,從索引i到索引j到索引k(不包括k)。Cap()
:返回Value的容量。Len()
:返回Value的長度。Index(i int)
:返回Value的第i個元素。MapIndex(key Value)
:返回Value中鍵為key的元素。MapKeys()
:返回Value的所有鍵。NumField()
:返回Value的字段數(shù)。Field(i int)
:返回Value的第i個字段。FieldByIndex(index []int)
:返回索引序列指定的嵌套字段。FieldByName(name string)
:返回Value中名為name的字段。FieldByNameFunc(match func(string) bool)
:返回Value中第一個字段名滿足函數(shù)match的字段。Recv()
:從通道接收值,返回接收到的值和是否成功接收。TryRecv()
:嘗試從通道接收值,不阻塞,返回接收到的值和是否成功接收。
六、反射的使用場景
1. 動態(tài)數(shù)據(jù)處理
反射的一個主要場景是處理動態(tài)數(shù)據(jù)結(jié)構(gòu),例如解析JSON或處理數(shù)據(jù)庫查詢結(jié)果。在這些場景中,數(shù)據(jù)的結(jié)構(gòu)在編譯時可能是未知的,因此需要使用反射來動態(tài)處理。
示例1:反射基本數(shù)據(jù)類型
package main import ( "fmt" "reflect" ) // 反射 /* Type : reflect.TypeOf(a) , 獲取變量的類型 Value :reflect.ValueOf(a) , 獲取變量的值 */ func main() { // 正常編程定義變量 var a int = 3 // func TypeOf(i any) Type 反射得到該變量的數(shù)據(jù)類型 fmt.Println("type", reflect.TypeOf(a)) // func ValueOf(i any) Value 反射得到該變量的值 fmt.Println("value", reflect.ValueOf(a)) // 根據(jù)反射的值,來獲取對象對應(yīng)的類型和數(shù)值 // 如果我們不知道這個對象的信息,我們可以通過這個對象拿到代碼中的一切。 // 獲取a的類型 v := reflect.ValueOf(a) // string int User // Kind : 獲取這個值的種類, 在反射中,所有數(shù)據(jù)類型判斷都是使用種類。 //根據(jù)kind來判斷其類型 if v.Kind() == reflect.Float64 { fmt.Println(v.Float()) } if v.Kind() == reflect.Int { fmt.Println(v.Int()) } fmt.Println(v.Kind() == reflect.Float64) //fmt.Println(v.Type()) }
如果取值時,類型不對,會報panic異常
示例2:解析JSON
package main import ( "encoding/json" "fmt" "reflect" ) type User struct { ID int `json:"id"` Name string `json:"name"` Age int `json:"age"` } func main() { jsonStr := `{"id":1, "name":"jigntian", "age":18}` var user User // func Unmarshal(data []byte, v any) error //先解析,看是否報錯,要是報錯,給的就不是json字符串 err := json.Unmarshal([]byte(jsonStr), &user) if err != nil { fmt.Println("Error:", err) return } // 使用反射獲取User結(jié)構(gòu)體的字段信息 val := reflect.ValueOf(user) typ := reflect.TypeOf(user) for i := 0; i < val.NumField(); i++ { fieldVal := val.Field(i) fieldType := typ.Field(i) fmt.Printf("Field Name: %s, Field Value: %v, Field Type: %s\n", fieldType.Name, fieldVal, fieldType.Type) } }
2. 獲取類型信息
通過reflect.TypeOf函數(shù),我們可以獲取變量的類型信息。reflect.Type類型提供了多種方法來獲取類型的詳細(xì)信息,如類型名稱、類型種類、字段信息等。
以下是一個示例,演示了如何獲取類型信息:
package main import ( "fmt" "reflect" ) type Person struct { Name string Age int } func main() { var p Person = Person{Name: "Alice", Age: 30} // 獲取類型信息 t := reflect.TypeOf(p) // 打印類型名稱 fmt.Println("Type name:", t.Name()) // 打印類型種類 fmt.Println("Type kind:", t.Kind()) // 打印結(jié)構(gòu)體字段信息 for i := 0; i < t.NumField(); i++ { field := t.Field(i) fmt.Printf("Field name: %s, Field type: %s\n", field.Name, field.Type) } }
在這個示例中,我們定義了一個Person結(jié)構(gòu)體,并創(chuàng)建了一個Person類型的變量p。通過reflect.TypeOf函數(shù),我們獲取了變量p的類型信息。然后,我們打印了類型名稱、類型種類以及結(jié)構(gòu)體字段信息。
獲取結(jié)構(gòu)體信息
package main import ( "fmt" "reflect" ) type User5 struct { Name string Age int Sex string } func (user User5) Say(msg string) { fmt.Println("User 說:", msg) } // PrintInfo 打印結(jié)構(gòu)體信息 func (user User5) PrintInfo() { fmt.Printf("姓名:%s,年齡:%d,性別:%s\n", user.Name, user.Age, user.Sex) } func main() { user := User5{"jingtian", 18, "男"} reflectGetInfo(user) } // 通過反射,獲取變量的信息 func reflectGetInfo(v interface{}) { // 1、獲取參數(shù)的類型Type , 可能是用戶自己定義的,但是Kind一定是內(nèi)部類型struct getType := reflect.TypeOf(v) fmt.Println(getType.Name()) // 類型信息 User5 fmt.Println(getType.Kind()) // 找到上級的種類Kind struct // 2、獲取值 getValue := reflect.ValueOf(v) //查看類型 Type得到的是我們自定義的類型main.User5 Kind得到的是go內(nèi)置的類型 struct fmt.Println("獲取到的value--type值類型", getValue.Type()) // main.User5 fmt.Println("獲取到的value--kind值類型", getValue.Kind()) // struct fmt.Println("獲取到value", getValue) // 獲取字段,通過Type扒出字段 // Type.NumField() 獲取這個類型中有幾個字段 3 // field(index) 得到字段的值 for i := 0; i < getType.NumField(); i++ { field := getType.Field(i) // 類型 //通過valueof.Field().Interface拿到值 value := getValue.Field(i).Interface() // value // 打印 fmt.Printf("字段名:%s,字段類型:%s,字段值:%v\n", field.Name, field.Type, value) } // 獲取這個結(jié)構(gòu)體的方法 , NumMethod 可以獲取方法的數(shù)量 for i := 0; i < getType.NumMethod(); i++ { method := getType.Method(i) fmt.Printf("方法的名字:%s\t,方法類型:%s", method.Name, method.Type) //執(zhí)行方法 //method.Name() } } /* 由上面反射,就可以推導(dǎo)出結(jié)構(gòu)體字段以及其方法 type User struct{ Name string Age int Sex string } func (main.User) PrintInfo(){} func (main.User) Say(string) */
通過反射可以 實(shí)現(xiàn)拿到一個對象,還原它的本身結(jié)構(gòu)信息。
3. 獲取值信息
通過reflect.ValueOf函數(shù),我們可以獲取變量的值信息。reflect.Value類型提供了多種方法來獲取和設(shè)置值,如獲取布爾值、整數(shù)值、浮點(diǎn)數(shù)值、字符串值等。此外,reflect.Value類型還提供了方法來操作結(jié)構(gòu)體字段、數(shù)組元素、映射鍵值對等。
以下是一個示例,演示了如何獲取值信息并操作結(jié)構(gòu)體字段:
package main import ( "fmt" "reflect" ) type Person3 struct { Name string Age int } func main() { var p Person3 = Person3{Name: "jingtian", Age: 18} // 獲取值信息 v := reflect.ValueOf(p) // 打印值信息 fmt.Println("Value of Name:", v.FieldByName("Name").String()) fmt.Println("Value of Age:", v.FieldByName("Age").Int()) // 修改結(jié)構(gòu)體字段值(注意:這里只是演示,實(shí)際上無法直接修改,因為v是不可變的) // 要修改值,需要使用reflect.ValueOf的Elem方法配合指針變量 // 下面的代碼會報錯:panic: reflect: call of reflect.Value.SetInt on zero Value // v.FieldByName("Age").SetInt(35) // 正確的修改方式 pv := reflect.ValueOf(&p).Elem() // 獲取指針指向的元素的值 pv.FieldByName("Age").SetInt(35) // 修改字段值 // 打印修改后的值 fmt.Println("Modified Age:", p.Age) }
在這個示例中,我們定義了一個Person3結(jié)構(gòu)體,并創(chuàng)建了一個Person3類型的變量p。通過reflect.ValueOf函數(shù),我們獲取了變量p的值信息。
然后,我們打印了結(jié)構(gòu)體字段的值。注意,由于reflect.ValueOf返回的值是不可變的,所以我們不能直接修改字段值。為了修改字段值,我們需要使用reflect.ValueOf的Elem方法配合指針變量來獲取可變的值。
4. 反射修改變量的值
通過反射修改值,需要操作對象的指針,拿到地址,然后拿到指針對象
通過Canset來判斷值是否可以被修改
package main import ( "fmt" "reflect" ) // 反射設(shè)置變量的值 func main() { var num float64 = 3.14 update(&num) //注意,這里傳入指針地址 fmt.Println(num) } func update(v any) { // 通過反射修改值,需要操作對象的指針,拿到地址,然后拿到指針對象 pointer := reflect.ValueOf(v) newValue := pointer.Elem() fmt.Println("類型:", newValue.Type()) fmt.Println("判斷該類型是否可以修改:", newValue.CanSet()) // 通過反射對象給變量賦值 //newValue.SetFloat(999.43434) // 也可以對類型進(jìn)行判斷,根據(jù)不同類型修改成不同的值 if newValue.Kind() == reflect.Float64 { // 通過反射對象給變量賦值 newValue.SetFloat(2.21) } if newValue.Kind() == reflect.Int { // 通過反射對象給變量賦值 newValue.SetInt(2) } }
修改結(jié)構(gòu)體變量:通過屬性名,來實(shí)現(xiàn)修改
package main import ( "fmt" "reflect" ) type Person4 struct { Name string Age int } // 反射設(shè)置變量的值 func main() { person := Person4{"jingtian", 18} //注意,這里傳入指針 update2(&person) fmt.Println(person) } func update2(v any) { // 通過反射修改值,需要操作對象的指針,拿到地址,然后拿到指針對象 pointer := reflect.ValueOf(v) newValue := pointer.Elem() fmt.Println("類型:", newValue.Type()) fmt.Println("判斷該類型是否可以修改:", newValue.CanSet()) //修改結(jié)構(gòu)體數(shù)據(jù) // 需要找到對象的結(jié)構(gòu)體字段名 newValue.FieldByName("Name").SetString("王安石") //如果不知道字段的類型,可以判斷下 fmt.Println("看下字段的類型", newValue.FieldByName("Name").Kind()) newValue.FieldByName("Age").SetInt(99) }
5. 反射調(diào)用方法
反射還可以用于在運(yùn)行時動態(tài)調(diào)用對象的方法。這在需要根據(jù)字符串名稱調(diào)用方法的場景下非常有用,例如實(shí)現(xiàn)一個簡單的命令行接口或基于插件的架構(gòu)。
通過方法名,找到這個方法, 然后調(diào)用 Call() 方法來執(zhí)行 包括無參方法和有參方法
package main import ( "fmt" "reflect" ) type User6 struct { Name string Age int Sex string } func (user User6) Say2(msg string) { fmt.Println(user.Name, "說:", msg) } // PrintInfo2 打印結(jié)構(gòu)體信息 func (user User6) PrintInfo2() { fmt.Printf("姓名:%s,年齡:%d,性別:%s\n", user.Name, user.Age, user.Sex) } func main() { user := User6{"景天", 18, "男"} // 通過方法名,找到這個方法, 然后調(diào)用 Call() 方法來執(zhí)行 // 反射調(diào)用方法 value := reflect.ValueOf(user) fmt.Printf("kind:%s, type:%s\n", value.Kind(), value.Type()) //根據(jù)方法名找到方法,通過call來調(diào)用方法,call里面跟參數(shù),。無參使用nil // func (v Value) Call(in []Value) []Value Call的參數(shù)是個reflect.Value類型的切片 value.MethodByName("PrintInfo2").Call(nil) // 無參方法調(diào)用 // 有參方法調(diào)用。先創(chuàng)建個reflect.Value類型的切片,長度為參數(shù)的個數(shù) args := make([]reflect.Value, 1) //給參數(shù)賦值 // func ValueOf(i any) Value args[0] = reflect.ValueOf("這反射來調(diào)用的") //找到方法名,調(diào)用傳參 value.MethodByName("Say2").Call(args) // 有參方法調(diào)用 }
6. 反射調(diào)用函數(shù)
reflect.ValueOf 通過函數(shù)名來進(jìn)行反射 得到函數(shù)名 reflect.ValueOf(函數(shù)名)
然后通過函數(shù)名.Call() 調(diào)用函數(shù)
package main import ( "fmt" "reflect" ) // 反射調(diào)用函數(shù) func func main() { // 通過函數(shù)名來進(jìn)行反射 reflect.ValueOf(函數(shù)名) // Kind func value1 := reflect.ValueOf(fun1) fmt.Println("打印拿到的數(shù)據(jù)類型", value1.Kind(), value1.Type()) //調(diào)用無參函數(shù) value1.Call(nil) //調(diào)用有參函數(shù) value2 := reflect.ValueOf(fun2) fmt.Println("打印有參函數(shù)", value2.Kind(), value2.Type()) //創(chuàng)建參數(shù) args1 := make([]reflect.Value, 2) args1[0] = reflect.ValueOf(1) args1[1] = reflect.ValueOf("hahahhaha") value2.Call(args1) //調(diào)用有參數(shù),有返回值函數(shù) vuale3 := reflect.ValueOf(fun3) fmt.Println(vuale3.Kind(), vuale3.Type()) args2 := make([]reflect.Value, 2) args2[0] = reflect.ValueOf(2) args2[1] = reflect.ValueOf("hahahhaha") //接收返回值 返回的是切片 // func (v Value) Call(in []Value) []Value resultValue := vuale3.Call(args2) fmt.Println("返回值:", resultValue[0]) } // 無參函數(shù) func fun1() { fmt.Println("fun1:無參") } // 有參函數(shù) func fun2(i int, s string) { fmt.Println("fun2:有參 i=", i, " s=", s) } // 有返回值函數(shù) func fun3(i int, s string) string { fmt.Println("fun3:有參有返回值 i=", i, " s=", s) return s }
七、反射的注意事項
性能開銷:反射相對于直接操作類型有更高的性能開銷,因為它需要在運(yùn)行時進(jìn)行類型檢查和值轉(zhuǎn)換。因此,在性能敏感的場景中應(yīng)謹(jǐn)慎使用反射。
安全性:反射允許程序在運(yùn)行時訪問和修改幾乎任何值,這可能導(dǎo)致意外的副作用或安全問題。因此,在使用反射時應(yīng)確保只訪問和修改預(yù)期的值,并避免潛在的類型沖突或數(shù)據(jù)損壞。
代碼可讀性:使用反射會使代碼變得更加復(fù)雜和難以閱讀。因此,在編寫代碼時應(yīng)權(quán)衡反射帶來的靈活性和代碼可讀性的重要性。
編譯時檢查:盡管反射提供了動態(tài)類型檢查和值操作的能力,但它無法替代編譯時類型檢查。因此,在編寫使用反射的代碼時,應(yīng)確保在編譯時盡可能多地檢查類型錯誤和邏輯錯誤。
八、總結(jié)
Go語言的反射功能提供了一種強(qiáng)大的機(jī)制來在運(yùn)行時動態(tài)檢查和操作值。通過反射,我們可以編寫更加靈活和通用的代碼來處理各種不同類型的值。然而,反射也帶來了性能開銷、安全性和代碼可讀性等挑戰(zhàn)。因此,在使用反射時應(yīng)謹(jǐn)慎權(quán)衡其優(yōu)缺點(diǎn),并根據(jù)具體場景做出合適的選擇。
以上就是Go語言中的反射原理解析與應(yīng)用的詳細(xì)內(nèi)容,更多關(guān)于Go反射原理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解Go語言如何實(shí)現(xiàn)類似Python中的with上下文管理器
熟悉?Python?的同學(xué)應(yīng)該知道?Python?中的上下文管理器非常好用,那么在?Go?中是否也能實(shí)現(xiàn)上下文管理器呢,下面小編就來和大家仔細(xì)講講吧2023-07-07使用Lumberjack+zap進(jìn)行日志切割歸檔操作
這篇文章主要介紹了使用Lumberjack+zap進(jìn)行日志切割歸檔操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12Go語言字典(map)用法實(shí)例分析【創(chuàng)建,填充,遍歷,查找,修改,刪除】
這篇文章主要介紹了Go語言字典(map)用法,結(jié)合實(shí)例形式較為詳細(xì)的分析了Go語言字典的創(chuàng)建、填充、遍歷、查找、修改、刪除等操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-02-02理解Golang中的數(shù)組(array)、切片(slice)和map
這篇文章主要介紹了理解Golang中的數(shù)組(array)、切片(slice)和map,本文先是給出代碼,然后一一分解,并給出一張內(nèi)圖加深理解,需要的朋友可以參考下2014-10-10Golang實(shí)現(xiàn)按比例切分流量的示例詳解
我們在進(jìn)行灰度發(fā)布時,往往需要轉(zhuǎn)發(fā)一部分流量到新上線的服務(wù)上,進(jìn)行小規(guī)模的驗證,隨著功能的不斷完善,我們也會逐漸增加轉(zhuǎn)發(fā)的流量,這就需要按比例去切分流量,那么如何實(shí)現(xiàn)流量切分呢,接下來小編就給大家詳細(xì)的介紹一下實(shí)現(xiàn)方法,需要的朋友可以參考下2023-09-09Golang 如何限制木馬圖片上傳服務(wù)器的實(shí)例
本文主要介紹了Golang 如何限制木馬圖片上傳服務(wù)器的實(shí)例,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02