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