百度做的網(wǎng)站字體侵權(quán)百度一下百度主頁官網(wǎng)
1.Golang可變參數(shù)
函數(shù)方法的參數(shù),可以是任意多個(gè),這種我們稱之為可以變參數(shù),比如我們常用的fmt.Println()這類函數(shù),可以接收一個(gè)可變的參數(shù)??梢宰儏?shù),可以是任意多個(gè)。我們自己也可以定義可以變參數(shù),可變參數(shù)的定義,在類型前加上省略號(hào)…即可。
func main() {print("1","2","3")
}func print (a ...interface{}){for _,v:=range a{fmt.Print(v)}fmt.Println()
}
例子中我們自己定義了一個(gè)接受可變參數(shù)的函數(shù),效果和fmt.Println()一樣??勺儏?shù)本質(zhì)上是一個(gè)數(shù)組,所以我們向使用數(shù)組一樣使用它,比如例子中的 for range 循環(huán)。
2.Golang Slice的底層實(shí)現(xiàn)
切片是基于數(shù)組實(shí)現(xiàn)的,它的底層是數(shù)組,它自己本身非常小,可以理解為對(duì)底層數(shù)組的抽象。因?yàn)榛跀?shù)組實(shí)現(xiàn),所以它的底層的內(nèi)存是連續(xù)分配的,效率非常高,還可以通過索引獲得數(shù)據(jù),可以迭代以及垃圾回收優(yōu)化。
切片本身并不是動(dòng)態(tài)數(shù)組或者數(shù)組指針。它內(nèi)部實(shí)現(xiàn)的數(shù)據(jù)結(jié)構(gòu)通過指針引用底層數(shù)組,設(shè)定相關(guān)屬性將數(shù)據(jù)讀寫操作限定在指定的區(qū)域內(nèi)。切片本身是一個(gè)只讀對(duì)象,其工作機(jī)制類似數(shù)組指針的一種封裝。
切片對(duì)象非常小,是因?yàn)樗侵挥?個(gè)字段的數(shù)據(jù)結(jié)構(gòu):
- 指向底層數(shù)組的指針
- 切片的長(zhǎng)度
- 切片的容量
這3個(gè)字段,就是Go語言操作底層數(shù)組的元數(shù)據(jù)。
3.Golang Slice的擴(kuò)容機(jī)制,有什么注意點(diǎn)
Go 中切片擴(kuò)容的策略是這樣的:
首先判斷,如果新申請(qǐng)容量大于 2 倍的舊容量,最終容量就是新申請(qǐng)的容量。否則判斷,如果舊切片的長(zhǎng)度小于 1024,則最終容量就是舊容量的兩倍。
否則判斷,如果舊切片長(zhǎng)度大于等于 1024,則最終容量從舊容量開始循環(huán)增加原來的 1/4 , 直到最終容量大于等于新申請(qǐng)的容量。如果最終容量計(jì)算值溢出,則最終容量就是新申請(qǐng)容量。
情況一:原數(shù)組還有容量可以擴(kuò)容(實(shí)際容量沒有填充完),這種情況下,擴(kuò)容以后的數(shù)組還是指向原來的數(shù)組,對(duì)一個(gè)切片的操作可能影響多個(gè)指針指向相同地址的Slice。
情況二:原來數(shù)組的容量已經(jīng)達(dá)到了最大值,再想擴(kuò)容, Go 默認(rèn)會(huì)先開一片內(nèi)存區(qū)域,把原來的值拷貝過來,然后再執(zhí)行 append() 操作。這種情況絲毫不影響原數(shù)組。
要復(fù)制一個(gè)Slice,最好使用Copy函數(shù)。
4.Golang Map底層實(shí)現(xiàn)
Golang 中 map 的底層實(shí)現(xiàn)是一個(gè)散列表,因此實(shí)現(xiàn) map 的過程實(shí)際上就是實(shí)現(xiàn)散表的過程。
在這個(gè)散列表中,主要出現(xiàn)的結(jié)構(gòu)體有兩個(gè),一個(gè)叫hmap(a header for a go map),一個(gè)叫bmap(a bucket for a Go map,通常叫其bucket)。
hmap如下所示:
圖中有很多字段,但是便于理解 map 的架構(gòu),你只需要關(guān)心的只有一個(gè),就是標(biāo)紅的字段:buckets 數(shù)組。Golang 的 map 中用于存儲(chǔ)的結(jié)構(gòu)是 bucket數(shù)組。而 bucket(即bmap)的結(jié)構(gòu)是怎樣的呢? bucket:
相比于 hmap,bucket 的結(jié)構(gòu)顯得簡(jiǎn)單一些,標(biāo)橙的字段依然是“核心”,我們使用的 map 中的 key 和 value 就存儲(chǔ)在這里。
“高位哈希值”數(shù)組記錄的是當(dāng)前 bucket 中 key 相關(guān)的”索引”,稍后會(huì)詳細(xì)敘述。還有一個(gè)字段是一個(gè)指向擴(kuò)容后的 bucket 的指針,使得 bucket 會(huì)形成一個(gè)鏈表結(jié)構(gòu)。
整體的結(jié)構(gòu)應(yīng)該是這樣的:
Golang 把求得的哈希值按照用途一分為二:高位和低位。低位用于尋找當(dāng)前 key屬于 hmap 中的哪個(gè) bucket,而高位用于尋找 bucket 中的哪個(gè) key。
需要特別指出的一點(diǎn)是:map中的key/value值都是存到同一個(gè)數(shù)組中的。這樣做的好處是:在key和value的長(zhǎng)度不同的時(shí)候,可以消除padding帶來的空間浪費(fèi)。
Map 的擴(kuò)容:當(dāng) Go 的 map 長(zhǎng)度增長(zhǎng)到大于加載因子所需的 map 長(zhǎng)度時(shí),Go 語言就會(huì)將產(chǎn)生一個(gè)新的 bucket 數(shù)組,然后把舊的 bucket 數(shù)組移到一個(gè)屬性字段 oldbucket中。
注意:并不是立刻把舊的數(shù)組中的元素轉(zhuǎn)義到新的 bucket 當(dāng)中,而是,只有當(dāng)訪問到具體的某個(gè) bucket 的時(shí)候,會(huì)把 bucket 中的數(shù)據(jù)轉(zhuǎn)移到新的 bucket 中。
5. JSON 標(biāo)準(zhǔn)庫對(duì) nil slice 和 空 slice 的處理是一致的嗎
首先 JSON 標(biāo)準(zhǔn)庫對(duì) nil slice 和 空 slice 的處理是不一致。
通常錯(cuò)誤的用法,會(huì)報(bào)數(shù)組越界的錯(cuò)誤,因?yàn)橹皇锹暶髁藄lice,卻沒有給實(shí)例化的對(duì)象。
var slice []int
slice[1] = 0
此時(shí)slice的值是nil,這種情況可以用于需要返回slice的函數(shù),當(dāng)函數(shù)出現(xiàn)異常的時(shí)候,保證函數(shù)依然會(huì)有nil的返回值。
empty slice 是指slice不為nil,但是slice沒有值,slice的底層的空間是空的,此時(shí)的定義如下
slice := make([]int,0)
slice := []int{}
當(dāng)我們查詢或者處理一個(gè)空的列表的時(shí)候,這非常有用,它會(huì)告訴我們返回的是一個(gè)列表,但是列表內(nèi)沒有任何值。總之,nil slice 和 empty slice是不同的東西,需要我們加以區(qū)分的。
6.Golang的內(nèi)存模型,為什么小對(duì)象多了會(huì)造成gc壓力
通常小對(duì)象過多會(huì)導(dǎo)致 GC 三色法消耗過多的GPU。優(yōu)化思路是,減少對(duì)象分配。
7.Data Race問題怎么解決?能不能不加鎖解決這個(gè)問題
同步訪問共享數(shù)據(jù)是處理數(shù)據(jù)競(jìng)爭(zhēng)的一種有效的方法。
golang在 1.1 之后引入了競(jìng)爭(zhēng)檢測(cè)機(jī)制,可以使用 go run -race 或者 go build -race來進(jìn)行靜態(tài)檢測(cè)。其在內(nèi)部的實(shí)現(xiàn)是,開啟多個(gè)協(xié)程執(zhí)行同一個(gè)命令, 并且記錄下每個(gè)變量的狀態(tài)。
競(jìng)爭(zhēng)檢測(cè)器基于C/C++的ThreadSanitizer 運(yùn)行時(shí)庫,該庫在Google內(nèi)部代碼基地和Chromium找到許多錯(cuò)誤。這個(gè)技術(shù)在2012年九月集成到Go中,從那時(shí)開始,它已經(jīng)在標(biāo)準(zhǔn)庫中檢測(cè)到42個(gè)競(jìng)爭(zhēng)條件?,F(xiàn)在,它已經(jīng)是我們持續(xù)構(gòu)建過程的一部分,當(dāng)競(jìng)爭(zhēng)條件出現(xiàn)時(shí),它會(huì)繼續(xù)捕捉到這些錯(cuò)誤。
競(jìng)爭(zhēng)檢測(cè)器已經(jīng)完全集成到Go工具鏈中,僅僅添加-race標(biāo)志到命令行就使用了檢測(cè)器。
$ go test -race mypkg // 測(cè)試包
$ go run -race mysrc.go // 編譯和運(yùn)行程序 $ go build -race mycmd
// 構(gòu)建程序 $ go install -race mypkg // 安裝程序
要想解決數(shù)據(jù)競(jìng)爭(zhēng)的問題可以使用互斥鎖sync.Mutex,解決數(shù)據(jù)競(jìng)爭(zhēng)(Data race),也可以使用管道解決,使用管道的效率要比互斥鎖高。
8.在 range 迭代 slice 時(shí),你怎么修改值的
在 range 迭代中,得到的值其實(shí)是元素的一份值拷貝,更新拷貝并不會(huì)更改原來的元素,即是拷貝的地址并不是原有元素的地址。
func main() {data := []int{1, 2, 3}for _, v := range data {v *= 10 // data 中原有元素是不會(huì)被修改的}fmt.Println("data: ", data) // data: [1 2 3]
}
如果要修改原有元素的值,應(yīng)該使用索引直接訪問。
func main() {data := []int{1, 2, 3}for i, v := range data {data[i] = v * 10 }fmt.Println("data: ", data) // data: [10 20 30]
}
如果你的集合保存的是指向值的指針,需稍作修改。依舊需要使用索引訪問元素,不過可以使用 range 出來的元素直接更新原有值。
func main() {data := []*struct{ num int }{{1}, {2}, {3},}for _, v := range data {v.num *= 10 // 直接使用指針更新}fmt.Println(data[0], data[1], data[2]) // &{10} &{20} &{30}
}
9.nil interface 和 nil interface 的區(qū)別
雖然 interface 看起來像指針類型,但它不是。interface 類型的變量只有在類型和值均為 nil 時(shí)才為 nil如果你的 interface 變量的值是跟隨其他變量變化的,與 nil 比較相等時(shí)小心。如果你的函數(shù)返回值類型是 interface,更要小心這個(gè)坑:
func main() {var data *bytevar in interface{}fmt.Println(data, data == nil) // <nil> truefmt.Println(in, in == nil) // <nil> truein = datafmt.Println(in, in == nil) // <nil> false // data 值為 nil,但 in 值不為 nil
}// 正確示例
func main() {doIt := func(arg int) interface{} {var result *struct{} = nilif arg > 0 {result = &struct{}{}} else {return nil // 明確指明返回 nil}return result}if res := doIt(-1); res != nil {fmt.Println("Good result: ", res)} else {fmt.Println("Bad result: ", res) // Bad result: <nil>}
}
10.select可以用于什么
常用于goroutine的完美退出。
golang 的 select 就是監(jiān)聽 IO 操作,當(dāng) IO 操作發(fā)生時(shí),觸發(fā)相應(yīng)的動(dòng)作每個(gè)case語句里必須是一個(gè)IO操作,確切的說,應(yīng)該是一個(gè)面向channel的IO操作。