Golang基礎(chǔ)常識性面試中常見的六大陷阱及應(yīng)對技巧總結(jié)
一、nil slice & empty slice
1、nil切片與空切片底層
nil切片:var nilSlice [] string
nil slice的長度len和容量cap都是0
nil slice==nil
nil slice的pointer是nil
空切片:emptySlice0 := make([]int,0)
empty slice的長度是0,容量是由指向底層數(shù)組決定
empty slice != nil
empty slice的pointer是底層數(shù)組的地址
nil切片和空切片最大的區(qū)別在指向的數(shù)組引用地址是不一樣的
nil空切片引用數(shù)組指針地址為0(無指向任何實(shí)際地址)
空切片的引用數(shù)組指針地址是有的,且固定為一個(gè)值,所有的空切片指向的數(shù)組引用地址都是一樣的
2、創(chuàng)建nil slice 和empty slice
package main import "fmt" func main() { var nilSlice []string // 創(chuàng)建一個(gè) nil 切片 emptySlice0 := make([]int, 0) // 方法1:創(chuàng)建一個(gè)空切片(零切片) var emptySlice1 = []string{} // 方法2:創(chuàng)建一個(gè)空切片 fmt.Printf("\nnilSlice---> Nil:%v Len:%d Capacity:%d", nilSlice == nil, len(nilSlice), cap(nilSlice)) fmt.Printf("\nemptySlice0---> nil:%v Len:%d Capacity:%d", emptySlice0 == nil, len(emptySlice0), cap(emptySlice0)) fmt.Printf("\nemptySlice1---> nil:%v Len:%d Capacity:%d", emptySlice1 == nil, len(emptySlice1), cap(emptySlice1)) // nil切片和空切片都可以正常 append數(shù)據(jù) nilSlice = append(nilSlice, "sss") } /* Nil:true Len:0 Capacity:0 nil:false Len:0 Capacity:0 nil:false Len:0 Capacity:0[sss] */
二、類型強(qiáng)轉(zhuǎn)產(chǎn)生內(nèi)存拷貝
1、字符串轉(zhuǎn)數(shù)組發(fā)送內(nèi)存拷貝
字符串轉(zhuǎn)成byte數(shù)組,會(huì)發(fā)生內(nèi)存拷貝嗎?
字符串轉(zhuǎn)出切片,會(huì)產(chǎn)生拷貝
嚴(yán)格來說,只要是發(fā)送類型強(qiáng)轉(zhuǎn)都會(huì)發(fā)送內(nèi)存拷貝
那么問題來了,頻繁的內(nèi)存拷貝操作聽起來對性能不大友好
有沒有什么辦法可以在字符串轉(zhuǎn)出切片的時(shí)候不用發(fā)生拷貝呢?
2、字符串轉(zhuǎn)數(shù)組不內(nèi)存拷貝方法
那么如果想要在底層轉(zhuǎn)換二者,只需要吧StringHeader的地址強(qiáng)轉(zhuǎn)成SliceHeader就行,那么go有個(gè)很強(qiáng)的包叫unsafe
1.
unsafe.Pointer(&a)
方法可以得到變量a
的地址。2.
(*reflect.StringHeader)(unsafe.Pointer(&a))
可以把字符串a(chǎn)轉(zhuǎn)成底層結(jié)構(gòu)的形式。3.
(*[]byte)(unsafe.Pointer(&ssh))
可以把ssh底層結(jié)構(gòu)體轉(zhuǎn)成byte的切片的指針。4.再通過
*
轉(zhuǎn)為指針指向的實(shí)際內(nèi)容。
package main import ( "fmt" "reflect" "unsafe" ) func main() { a :="aaa" ssh := *(*reflect.StringHeader)(unsafe.Pointer(&a)) b := *(*[]byte)(unsafe.Pointer(&ssh)) fmt.Printf("%v---%T",b,b) // [97 97 97]---[]uint8 }
三、拷貝大切片一定代價(jià)大嗎?
SliceHeader
是切片
在go的底層結(jié)構(gòu)。第一個(gè)字是指向切片底層數(shù)組的指針,這是切片的存儲空間
第二個(gè)字段是切片的長度
第三個(gè)字段是容量
type SliceHeader struct { Data uintptr Len int Cap int }
大切片跟小切片的區(qū)別無非就是
Len
和Cap
的值比小切片的這兩個(gè)值大一些,如果發(fā)生拷貝,本質(zhì)上就是拷貝上面的三個(gè)字段。所以 拷貝大切片跟小切片的代價(jià)應(yīng)該是一樣的
四、map不初始化使用會(huì)怎么樣
空map和nil map結(jié)果是一樣的,都為map[]。
所以,這個(gè)時(shí)候別斷定map是空還是nil,而應(yīng)該通過map == nil來判斷。
package main func main() { var m1 map[string]string // 創(chuàng)建一個(gè) nil map println("m1為nil: ", m1==nil) // 報(bào)錯(cuò) => panic: assignment to entry in nil map //m1["name"] = "tom" var m2 = make(map[string]string) // 創(chuàng)建一個(gè)空map m2["name"] = "jack" // 空map可以正常 println("m2為nil: ", m2==nil) }
五、map會(huì)遍歷刪除安全嗎?
map 并不是一個(gè)線程安全的數(shù)據(jù)結(jié)構(gòu)。
同時(shí)讀寫一個(gè) map 是未定義的行為,如果被檢測到,會(huì)直接 panic。
上面說的是發(fā)生在多個(gè)協(xié)程同時(shí)讀寫同一個(gè) map 的情況下。
如果在同一個(gè)協(xié)程內(nèi)邊遍歷邊刪除,并不會(huì)檢測到同時(shí)讀寫,理論上是可以這樣做的。
sync.Map可以解決多線程讀寫map問題
一般而言,這可以通過讀寫鎖來解決:
sync.RWMutex
。讀之前調(diào)用
RLock()
函數(shù),讀完之后調(diào)用RUnlock()
函數(shù)解鎖;寫之前調(diào)用
Lock()
函數(shù),寫完之后,調(diào)用Unlock()
解鎖。另外,
sync.Map
是線程安全的 map,也可以使用
六、for循環(huán)append坑
1、坑1:添加元素變覆蓋
不會(huì)死循環(huán),
for range
其實(shí)是golang
的語法糖
,在循環(huán)開始前會(huì)獲取切片的長度len(切片)
,然后再執(zhí)行len(切片)
次數(shù)的循環(huán)。
package main import "fmt" func main() { s := []int{1,2,3,4,5} for _, v:=range s { s =append(s, v) fmt.Printf("len(s)=%v\n",len(s)) } } /* len(s)=6 len(s)=7 len(s)=8 len(s)=9 len(s)=10 */
2、坑2:值全部一樣
每次循轉(zhuǎn)中num的值是正常的,但是由append構(gòu)造的res中,全是nums的最后一個(gè)值。
最終總結(jié)出原因是在for range語句中,創(chuàng)建了變量num且只被創(chuàng)建了一次。
即num有自己的空間內(nèi)存且地址在for循環(huán)過程中不變
循環(huán)過程中每次將nums中對應(yīng)的值和num進(jìn)行值傳遞
package main import "fmt" func main() { var nums = []int{1, 2, 3, 4, 5} var res []*int for _, num := range nums { res = append(res, &num) //fmt.Println("num:", num) } for _, r := range res { fmt.Println("res:", *r) } } /* res: 5 res: 5 res: 5 res: 5 res: 5 */
3、解決方法
方法1
不使用for range的形式,直接用索引來對nums取值
package main import "fmt" func main() { var nums = []int{1, 2, 3, 4, 5} var res []*int for i := 0; i < len(nums); i++ { res = append(res, &nums[i]) } fmt.Println("res:", res) for _, r := range res { fmt.Println("res:", *r) } }
方法2
在for循環(huán)中每次再定義一個(gè)新的變量num_temp,將num的值傳給num_temp,之后append該變量即可。
package main import "fmt" func main() { var nums = []int{1, 2, 3, 4, 5} var res []*int for _, num := range nums { numTemp := num // 創(chuàng)建一個(gè)新的臨時(shí)變量 res = append(res, &numTemp) } for _, r := range res { fmt.Println("res:", *r) } } /* res: 1 res: 2 res: 3 res: 4 res: 5 */
總結(jié)
到此這篇關(guān)于Golang基礎(chǔ)常識性面試中常見的六大陷阱及應(yīng)對技巧總結(jié)的文章就介紹到這了,更多相關(guān)Golang面試常見陷阱及應(yīng)對內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang使用sync.singleflight解決熱點(diǎn)緩存穿透問題
在go的sync包中,有一個(gè)singleflight包,里面有一個(gè)?singleflight.go文件,代碼加注釋,一共200行出頭,通過?singleflight可以很容易實(shí)現(xiàn)緩存和去重的效果,避免重復(fù)計(jì)算,接下來我們就給大家詳細(xì)介紹一下sync.singleflight如何解決熱點(diǎn)緩存穿透問題2023-07-07golang讀取yaml配置文件的方法實(shí)現(xiàn)
本文主要介紹了golang讀取yaml配置文件的方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-10-10Golang中Set類型的實(shí)現(xiàn)方法示例詳解
這篇文章主要給大家介紹了關(guān)于Golang中Set類型實(shí)現(xiàn)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09淺析Go使用定時(shí)器時(shí)如何避免潛在的內(nèi)存泄漏陷阱
這篇文章來和大家一起探討一下Go?中如何高效使用?timer,特別是與select?一起使用時(shí),如何防止?jié)撛诘膬?nèi)存泄漏問題,感興趣的可以了解下2024-01-01Go語言如何使用golang-jwt/jwt/v4進(jìn)行JWT鑒權(quán)詳解
最近項(xiàng)目中需要用到鑒權(quán)機(jī)制,golang中jwt可以用,這篇文章主要給大家介紹了關(guān)于Go語言如何使用golang-jwt/jwt/v4進(jìn)行JWT鑒權(quán)的相關(guān)資料,需要的朋友可以參考下2022-09-09