最新大廠 Go 面試題 50 問

前言
Golang 這門語言想必大家都不陌生,現(xiàn)在也比較火,學習的人也比較多。作為一款性能還算不錯的語言,現(xiàn)在很多大廠的新項目都選擇了Golang。
這期針對大家的疑惑,總結(jié)了大廠系列Golang語法50問,供大家面試和學習用,下面看一下提綱。


1.使用值為?nil?的?slice、map會發(fā)生啥
允許對值為 nil 的 slice 添加元素,但對值為 nil 的 map 添加元素,則會造成運行時 panic。
//?map?錯誤示例
func?main()?{
????var?m?map[string]int
????m["one"]?=?1??//?error:?panic:?assignment?to?entry?in?nil?map
????//?m?:=?make(map[string]int)//?map?的正確聲明,分配了實際的內(nèi)存
}????
?
//?slice?正確示例
func?main()?{
?var?s?[]int
?s?=?append(s,?1)
}
2.訪問 map 中的 key,需要注意啥
當訪問 map 中不存在的 key 時,Go 則會返回元素對應(yīng)數(shù)據(jù)類型的零值,比如 nil、''?、false 和?0,取值操作總有值返回,故不能通過取出來的值,來判斷 key 是不是在 map 中。
檢查 key 是否存在可以用 map 直接訪問,檢查返回的第二個參數(shù)即可。
//?錯誤的?key?檢測方式
func?main()?{
?x?:=?map[string]string{"one":?"2",?"two":?"",?"three":?"3"}
?if?v?:=?x["two"];?v?==?""?{
??fmt.Println("key?two?is?no?entry")?//?鍵?two?存不存在都會返回的空字符串
?}
}
?
//?正確示例
func?main()?{
?x?:=?map[string]string{"one":?"2",?"two":?"",?"three":?"3"}
?if?_,?ok?:=?x["two"];?!ok?{
??fmt.Println("key?two?is?no?entry")
?}
}
3.string 類型的值可以修改嗎
不能,嘗試使用索引遍歷字符串,來更新字符串中的個別字符,是不允許的。
string 類型的值是只讀的二進制 byte slice,如果真要修改字符串中的字符,將 string 轉(zhuǎn)為?[]byte 修改后,再轉(zhuǎn)為 string 即可。
//?修改字符串的錯誤示例
func?main()?{
?x?:=?"text"
?x[0]?=?"T"??//?error:?cannot?assign?to?x[0]
?fmt.Println(x)
}
?
?
//?修改示例
func?main()?{
?x?:=?"text"
?xBytes?:=?[]byte(x)
?xBytes[0]?=?'T'?//?注意此時的?T?是?rune?類型
?x?=?string(xBytes)
?fmt.Println(x)?//?Text
}
4.switch 中如何強制執(zhí)行下一個 case 代碼塊
switch 語句中的 case 代碼塊會默認帶上 break,但可以使用 fallthrough 來強制執(zhí)行下一個 case 代碼塊。
func?main()?{
?isSpace?:=?func(char?byte)?bool?{
??switch?char?{
??case?'?':?//?空格符會直接?break,返回?false?//?和其他語言不一樣
??//?fallthrough?//?返回?true
??case?'\t':
???return?true
??}
??return?false
?}
?fmt.Println(isSpace('\t'))?//?true
?fmt.Println(isSpace('?'))?//?false
}
5.你是如何關(guān)閉 HTTP 的響應(yīng)體的
直接在處理 HTTP 響應(yīng)錯誤的代碼塊中,直接關(guān)閉非 nil 的響應(yīng)體;手動調(diào)用 defer 來關(guān)閉響應(yīng)體。
//?正確示例
func?main()?{
?resp,?err?:=?http.Get("http://www.baidu.com")
?
????//?關(guān)閉?resp.Body?的正確姿勢
????if?resp?!=?nil?{
??defer?resp.Body.Close()
?}
?
?checkError(err)
?defer?resp.Body.Close()
?
?body,?err?:=?ioutil.ReadAll(resp.Body)
?checkError(err)
?
?fmt.Println(string(body))
}
6.你是否主動關(guān)閉過http連接,為啥要這樣做
有關(guān)閉,不關(guān)閉會程序可能會消耗完 socket 描述符。有如下2種關(guān)閉方式:
直接設(shè)置請求變量的 Close 字段值為 true,每次請求結(jié)束后就會主動關(guān)閉連接。
設(shè)置?Header?請求頭部選項?Connection:?close,然后服務(wù)器返回的響應(yīng)頭部也會有這個選項,此時?HTTP?標準庫會主動斷開連接
//?主動關(guān)閉連接
func?main()?{
?req,?err?:=?http.NewRequest("GET",?"http://golang.org",?nil)
?checkError(err)
?
?req.Close?=?true
?//req.Header.Add("Connection",?"close")?//?等效的關(guān)閉方式
?
?resp,?err?:=?http.DefaultClient.Do(req)
?if?resp?!=?nil?{
??defer?resp.Body.Close()
?}
?checkError(err)
?
?body,?err?:=?ioutil.ReadAll(resp.Body)
?checkError(err)
?
?fmt.Println(string(body))
}
你可以創(chuàng)建一個自定義配置的 HTTP transport 客戶端,用來取消 HTTP 全局的復用連接。
func?main()?{
?tr?:=?http.Transport{DisableKeepAlives:?true}
?client?:=?http.Client{Transport:?&tr}
?
?resp,?err?:=?client.Get("https://golang.google.cn/")
?if?resp?!=?nil?{
??defer?resp.Body.Close()
?}
?checkError(err)
?
?fmt.Println(resp.StatusCode)?//?200
?
?body,?err?:=?ioutil.ReadAll(resp.Body)
?checkError(err)
?
?fmt.Println(len(string(body)))
}7.解析 JSON 數(shù)據(jù)時,默認將數(shù)值當做哪種類型
在 encode/decode JSON 數(shù)據(jù)時,Go 默認會將數(shù)值當做 float64 處理。
func?main()?{
?????var?data?=?[]byte(`{"status":?200}`)
?????var?result?map[string]interface{}
?????if?err?:=?json.Unmarshal(data,?&result);?err?!=?nil?{
?????log.Fatalln(err)
}
解析出來的 200 是 float 類型。
8.如何從 panic 中恢復
在一個 defer 延遲執(zhí)行的函數(shù)中調(diào)用 recover ,它便能捕捉/中斷 panic。
//?錯誤的?recover?調(diào)用示例
func?main()?{
?recover()?//?什么都不會捕捉
?panic("not?good")?//?發(fā)生?panic,主程序退出
?recover()?//?不會被執(zhí)行
?println("ok")
}
?
//?正確的?recover?調(diào)用示例
func?main()?{
?defer?func()?{
??fmt.Println("recovered:?",?recover())
?}()
?panic("not?good")
}
9.簡短聲明的變量需要注意啥
簡短聲明的變量只能在函數(shù)內(nèi)部使用
struct?的變量字段不能使用?:=?來賦值
不能用簡短聲明方式來單獨為一個變量重復聲明,?:=?左側(cè)至少有一個新變量,才允許多變量的重復聲明
10.range?迭代 map是有序的嗎
無序的。Go 的運行時是有意打亂迭代順序的,所以你得到的迭代結(jié)果可能不一致。但也并不總會打亂,得到連續(xù)相同的 5 個迭代結(jié)果也是可能的。
11.recover的執(zhí)行時機
無,recover 必須在?defer 函數(shù)中運行。recover 捕獲的是祖父級調(diào)用時的異常,直接調(diào)用時無效。
func?main()?{
????recover()
????panic(1)
}
直接 defer 調(diào)用也是無效。
func?main()?{
????defer?recover()
????panic(1)
}
defer 調(diào)用時多層嵌套依然無效。
func?main()?{
????defer?func()?{
????????func()?{?recover()?}()
????}()
????panic(1)
}
必須在 defer 函數(shù)中直接調(diào)用才有效。
func?main()?{
????defer?func()?{
????????recover()
????}()
????panic(1)
}
12.閉包錯誤引用同一個變量問題怎么處理
在每輪迭代中生成一個局部變量 i 。如果沒有 i := i 這行,將會打印同一個變量。?
func?main()?{
????for?i?:=?0;?i?5;?i++?{
????????i?:=?i
????????defer?func()?{
????????????println(i)
????????}()
????}
}
或者是通過函數(shù)參數(shù)傳入 i 。
func?main()?{
????for?i?:=?0;?i?5;?i++?{
????????defer?func(i?int)?{
????????????println(i)
????????}(i)
????}
}
13.在循環(huán)內(nèi)部執(zhí)行defer語句會發(fā)生啥
defer 在函數(shù)退出時才能執(zhí)行,在 for 執(zhí)行 defer 會導致資源延遲釋放。
func?main()?{
????for?i?:=?0;?i?5;?i++?{
????????func()?{
????????????f,?err?:=?os.Open("/path/to/file")
????????????if?err?!=?nil?{
????????????????log.Fatal(err)
????????????}
????????????defer?f.Close()
????????}()
????}
}
func 是一個局部函數(shù),在局部函數(shù)里面執(zhí)行 defer 將不會有問題。
14.說出一個避免Goroutine泄露的措施
可以通過 context 包來避免內(nèi)存泄漏。
func?main()?{
????ctx,?cancel?:=?context.WithCancel(context.Background())
????ch?:=?func(ctx?context.Context)?<-chan?int?{
????????ch?:=?make(chan?int)
????????go?func()?{
????????????for?i?:=?0;?;?i++?{
????????????????select?{
????????????????case?<-?ctx.Done():
????????????????????return
????????????????case?ch?<-?i:
????????????????}
????????????}
????????}?()
????????return?ch
????}(ctx)
????for?v?:=?range?ch?{
????????fmt.Println(v)
????????if?v?==?5?{
????????????cancel()
????????????break
????????}
????}
}
下面的 for 循環(huán)停止取數(shù)據(jù)時,就用 cancel 函數(shù),讓另一個協(xié)程停止寫數(shù)據(jù)。如果下面 for 已停止讀取數(shù)據(jù),上面 for 循環(huán)還在寫入,就會造成內(nèi)存泄漏。
15.如何跳出for select 循環(huán)
通常在for循環(huán)中,使用break可以跳出循環(huán),但是注意在go語言中,for select配合時,break 并不能跳出循環(huán)。
func?testSelectFor2(chExit?chan?bool){
?EXIT:
????for??{
????????select?{
????????case?v,?ok?:=?<-chExit:
????????????if?!ok?{
????????????????fmt.Println("close?channel?2",?v)
????????????????break?EXIT//goto?EXIT2
????????????}
?
????????????fmt.Println("ch2?val?=",?v)
????????}
????}
?
????//EXIT2:
????fmt.Println("exit?testSelectFor2")
}
16.如何在切片中查找
go中使用 sort.searchXXX 方法,在排序好的切片中查找指定的方法,但是其返回是對應(yīng)的查找元素不存在時,待插入的位置下標(元素插入在返回下標前)。
可以通過封裝如下函數(shù),達到目的。
func?IsExist(s?[]string,?t?string)?(int,?bool)?{
????iIndex?:=?sort.SearchStrings(s,?t)
????bExist?:=?iIndex!=len(s)?&&?s[iIndex]==t
?
????return?iIndex,?bExist
}
17.如何初始化帶嵌套結(jié)構(gòu)的結(jié)構(gòu)體
go 的哲學是組合優(yōu)于繼承,使用 struct 嵌套即可完成組合,內(nèi)嵌的結(jié)構(gòu)體屬性就像外層結(jié)構(gòu)的屬性即可,可以直接調(diào)用。
注意初始化外層結(jié)構(gòu)體時,必須指定內(nèi)嵌結(jié)構(gòu)體名稱的結(jié)構(gòu)體初始化,如下看到 s1方式報錯,s2 方式正確。
type?stPeople?struct?{
????Gender?bool
????Name?string
}
?
type?stStudent?struct?{
????stPeople
????Class?int
}
?
//嘗試4?嵌套結(jié)構(gòu)的初始化表達式
//var?s1?=?stStudent{false,?"JimWen",?3}
var?s2?=?stStudent{stPeople{false,?"JimWen"},?3}
fmt.Println(s2.Gender,?s2.Name,?s2.Class)
18.切片和數(shù)組的區(qū)別
數(shù)組是具有固定長度,且擁有零個或者多個,相同數(shù)據(jù)類型元素的序列。數(shù)組的長度是數(shù)組類型的一部分,所以[3]int 和 [4]int 是兩種不同的數(shù)組類型。
數(shù)組需要指定大小,不指定也會根據(jù)初始化的自動推算出大小,不可改變;數(shù)組是值傳遞。
數(shù)組是內(nèi)置類型,是一組同類型數(shù)據(jù)的集合,它是值類型,通過從0開始的下標索引訪問元素值。在初始化后長度是固定的,無法修改其長度。
當作為方法的參數(shù)傳入時將復制一份數(shù)組而不是引用同一指針。數(shù)組的長度也是其類型的一部分,通過內(nèi)置函數(shù)len(array)獲取其長度。
數(shù)組定義:
var array [10]int
var array =[5]int{1,2,3,4,5}
切片表示一個擁有相同類型元素的可變長度的序列。切片是一種輕量級的數(shù)據(jù)結(jié)構(gòu),它有三個屬性:指針、長度和容量。
切片不需要指定大??;切片是地址傳遞;切片可以通過數(shù)組來初始化,也可以通過內(nèi)置函數(shù)make()初始化 。初始化時len=cap,在追加元素時如果容量cap不足時將按len的2倍擴容。
切片定義:
var slice []type = make([]type, len)
19.new和make的區(qū)別
new 的作用是初始化一個指向類型的指針 (*T) 。
new 函數(shù)是內(nèi)建函數(shù),函數(shù)定義:func new(Type) *Type。
使用 new 函數(shù)來分配空間。傳遞給 new 函數(shù)的是一個類型,不是一個值。返回值是指向這個新分配的零值的指針。
make 的作用是為 slice,map 或 chan 初始化并返回引用 (T)。
make 函數(shù)是內(nèi)建函數(shù),函數(shù)定義:func make(Type, size IntegerType) Type;第一個參數(shù)是一個類型,第二個參數(shù)是長度;返回值是一個類型。
make(T, args) 函數(shù)的目的與 new(T) 不同。它僅僅用于創(chuàng)建 Slice, Map 和 Channel,并且返回類型是 T(不是T*)的一個初始化的(不是零值)的實例。
20.Printf()、Sprintf()、Fprintf()函數(shù)的區(qū)別用法是什么
都是把格式好的字符串輸出,只是輸出的目標不一樣。
Printf(),是把格式字符串輸出到標準輸出(一般是屏幕,可以重定向)。Printf() 是和標準輸出文件 (stdout) 關(guān)聯(lián)的,F(xiàn)printf ?則沒有這個限制。
Sprintf(),是把格式字符串輸出到指定字符串中,所以參數(shù)比printf多一個char*。那就是目標字符串地址。
Fprintf(),是把格式字符串輸出到指定文件設(shè)備中,所以參數(shù)比 printf 多一個文件指針 FILE*。主要用于文件操作。Fprintf() 是格式化輸出到一個stream,通常是到文件。
21.說說go語言中的for循環(huán)
for 循環(huán)支持 continue 和 break 來控制循環(huán),但是它提供了一個更高級的break,可以選擇中斷哪一個循環(huán) for 循環(huán)不支持以逗號為間隔的多個賦值語句,必須使用平行賦值的方式來初始化多個變量。
22.Array?類型的值作為函數(shù)參數(shù)
在 C/C++?中,數(shù)組(名)是指針。將數(shù)組作為參數(shù)傳進函數(shù)時,相當于傳遞了數(shù)組內(nèi)存地址的引用,在函數(shù)內(nèi)部會改變該數(shù)組的值。
在 Go 中,數(shù)組是值。作為參數(shù)傳進函數(shù)時,傳遞的是數(shù)組的原始值拷貝,此時在函數(shù)內(nèi)部是無法更新該數(shù)組的。
//?數(shù)組使用值拷貝傳參
func?main()?{
?x?:=?[3]int{1,2,3}
?
?func(arr?[3]int)?{
??arr[0]?=?7
??fmt.Println(arr)?//?[7?2?3]
?}(x)
?fmt.Println(x)???//?[1?2?3]?//?并不是你以為的?[7?2?3]
}
想改變數(shù)組,直接傳遞指向這個數(shù)組的指針類型。
//?傳址會修改原數(shù)據(jù)
func?main()?{
?x?:=?[3]int{1,2,3}
?
?func(arr?*[3]int)?{
??(*arr)[0]?=?7?
??fmt.Println(arr)?//?&[7?2?3]
?}(&x)
?fmt.Println(x)?//?[7?2?3]
}
直接使用 slice:即使函數(shù)內(nèi)部得到的是 slice 的值拷貝,但依舊會更新 slice 的原始數(shù)據(jù)(底層 array)
//?錯誤示例
func?main()?{
?x?:=?[]string{"a",?"b",?"c"}
?for?v?:=?range?x?{
??fmt.Println(v)?//?1?2?3
?}
}
?
?
//?正確示例
func?main()?{
?x?:=?[]string{"a",?"b",?"c"}
?for?_,?v?:=?range?x?{?//?使用?_?丟棄索引
??fmt.Println(v)
?}
}說。go語言中的for循
23.說說go語言中的switch語句
單個 case 中,可以出現(xiàn)多個結(jié)果選項。只有在 case 中明確添加 fallthrough關(guān)鍵字,才會繼續(xù)執(zhí)行緊跟的下一個 case。
24.說說go語言中有沒有隱藏的this指針
方法施加的對象顯式傳遞,沒有被隱藏起來。
golang 的面向?qū)ο蟊磉_更直觀,對于面向過程只是換了一種語法形式來表達
方法施加的對象不需要非得是指針,也不用非得叫 this。
25.go語言中的引用類型包含哪些
數(shù)組切片、字典(map)、通道(channel)、接口(interface)。
26.go語言中指針運算有哪些
可以通過“&”取指針的地址;可以通過“*”取指針指向的數(shù)據(jù)。
26.說說go語言的main函數(shù)
main 函數(shù)不能帶參數(shù);main 函數(shù)不能定義返回值。
main 函數(shù)所在的包必須為 main 包;main 函數(shù)中可以使用 flag 包來獲取和解析命令行參數(shù)。
27.go語言觸發(fā)異常的場景有哪些
空指針解析
下標越界
除數(shù)為0
調(diào)用 panic 函數(shù)
28.說說go語言的beego框架
beego 是一個 golang 實現(xiàn)的輕量級HTTP框架
beego 可以通過注釋路由、正則路由等多種方式完成 url 路由注入
可以使用 bee new 工具生成空工程,然后使用 bee run 命令自動熱編譯
29.說說go語言的goconvey框架
goconvey 是一個支持 golang 的單元測試框架
goconvey 能夠自動監(jiān)控文件修改并啟動測試,并可以將測試結(jié)果實時輸出到web界面
goconvey 提供了豐富的斷言簡化測試用例的編寫
30.GoStub的作用是什么
GoStub 可以對全局變量打樁
GoStub 可以對函數(shù)打樁
GoStub 不可以對類的成員方法打樁
GoStub 可以打動態(tài)樁,比如對一個函數(shù)打樁后,多次調(diào)用該函數(shù)會有不同的行為
31.go語言編程的好處是什么
編譯和運行都很快。
在語言層級支持并行操作。
有垃圾處理器。
內(nèi)置字符串和 maps。
函數(shù)是 go 語言的最基本編程單位。
32.說說go語言的select機制
select 機制用來處理異步 IO 問題
select 機制最大的一條限制就是每個 case 語句里必須是一個 IO 操作
golang 在語言級別支持 select 關(guān)鍵字
33.解釋一下go語言中的靜態(tài)類型聲明
靜態(tài)類型聲明是告訴編譯器不需要太多的關(guān)注這個變量的細節(jié)。
靜態(tài)變量的聲明,只是針對于編譯的時候, 在連接程序的時候,編譯器還要對這個變量進行實際的聲明。
34.go的接口是什么
在 go 語言中,interface 也就是接口,被用來指定一個對象。接口具有下面的要素:
一系列的方法
具體應(yīng)用中并用來表示某個數(shù)據(jù)類型
在 go 中使用 interface 來實現(xiàn)多態(tài)
35.Go語言里面的類型斷言是怎么回事
類型斷言是用來從一個接口里面讀取數(shù)值給一個具體的類型變量。
類型轉(zhuǎn)換是指轉(zhuǎn)換兩個不相同的數(shù)據(jù)類型。
36.go語言中局部變量和全局變量的缺省值是什么
全局變量的缺省值是與這個類型相關(guān)的零值。
37.go語言編程的好處是什么
編譯和運行都很快。
在語言層級支持并行操作。
有垃圾處理器。
內(nèi)置字符串和 maps。
函數(shù)是 go 語言的最基本編程單位。
38.解釋一下go語言中的靜態(tài)類型聲明
靜態(tài)類型聲明是告訴編譯器不需要太多的關(guān)注這個變量的細節(jié)。
靜態(tài)變量的聲明,只是針對于編譯的時候, 在連接程序的時候,編譯器還要對這個變量進行實際的聲明。
39.模塊化編程是怎么回事
模塊化編程是指把一個大的程序分解成幾個小的程序。這么做的目的是為了減少程序的復雜度,易于維護,并且達到最高的效率。
碼字不易,請不吝點贊,隨手關(guān)注,更多精彩,自動送達。
40.Golang的方法有什么特別之處
函數(shù)的定義聲明沒有接收者。
方法的聲明和函數(shù)類似,他們的區(qū)別是:方法在定義的時候,會在func和方法名之間增加一個參數(shù),這個參數(shù)就是接收者,這樣我們定義的這個方法就和接收者綁定在了一起,稱之為這個接收者的方法。
Go語言里有兩種類型的接收者:值接收者和指針接收者。
使用值類型接收者定義的方法,在調(diào)用的時候,使用的其實是值接收者的一個副本,所以對該值的任何操作,不會影響原來的類型變量。-------相當于形式參數(shù)。
如果我們使用一個指針作為接收者,那么就會其作用了,因為指針接收者傳遞的是一個指向原值指針的副本,指針的副本,指向的還是原來類型的值,所以修改時,同時也會影響原來類型變量的值。
41.Golang可變參數(shù)
func?main()?{
?print("1","2","3")
}
func?print?(a?...interface{}){
?for?_,v:=range?a{
??fmt.Print(v)
?}
?fmt.Println()
}
例子中我們自己定義了一個接受可變參數(shù)的函數(shù),效果和fmt.Println()一樣。
42.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ù)。

43.Golang Slice的擴容機制,有什么注意點
Go 中切片擴容的策略是這樣的:
首先判斷,如果新申請容量大于 2 倍的舊容量,最終容量就是新申請的容量。
否則判斷,如果舊切片的長度小于 1024,則最終容量就是舊容量的兩倍。
否則判斷,如果舊切片長度大于等于 1024,則最終容量從舊容量開始循環(huán)增加原來的? 1/4 , 直到最終容量大于等于新申請的容量。
如果最終容量計算值溢出,則最終容量就是新申請容量。
情況一:
原數(shù)組還有容量可以擴容(實際容量沒有填充完),這種情況下,擴容以后的數(shù)組還是指向原來的數(shù)組,對一個切片的操作可能影響多個指針指向相同地址的Slice。
情況二:
原來數(shù)組的容量已經(jīng)達到了最大值,再想擴容, Go 默認會先開一片內(nèi)存區(qū)域,把原來的值拷貝過來,然后再執(zhí)行 append() 操作。這種情況絲毫不影響原數(shù)組。
要復制一個Slice,最好使用Copy函數(shù)。
44.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)心的只有一個,就是標紅的字段:buckets 數(shù)組。Golang 的 map 中用于存儲的結(jié)構(gòu)是 bucket數(shù)組。而 bucket(即bmap)的結(jié)構(gòu)是怎樣的呢?
bucket:

相比于 hmap,bucket 的結(jié)構(gòu)顯得簡單一些,標橙的字段依然是“核心”,我們使用的 map 中的 key 和 value 就存儲在這里。
"高位哈希值"數(shù)組記錄的是當前 bucket 中 key 相關(guān)的"索引",稍后會詳細敘述。還有一個字段是一個指向擴容后的 bucket 的指針,使得 bucket 會形成一個鏈表結(jié)構(gòu)。
整體的結(jié)構(gòu)應(yīng)該是這樣的:

Golang 把求得的哈希值按照用途一分為二:高位和低位。低位用于尋找當前 key屬于 hmap 中的哪個 bucket,而高位用于尋找 bucket 中的哪個 key。
需要特別指出的一點是:map中的key/value值都是存到同一個數(shù)組中的。這樣做的好處是:在key和value的長度不同的時候,可以消除padding帶來的空間浪費。

Map 的擴容:
當 Go 的 map 長度增長到大于加載因子所需的 map 長度時,Go 語言就會將產(chǎn)生一個新的 bucket 數(shù)組,然后把舊的 bucket 數(shù)組移到一個屬性字段 oldbucket中。
注意:并不是立刻把舊的數(shù)組中的元素轉(zhuǎn)義到新的 bucket 當中,而是,只有當訪問到具體的某個 bucket 的時候,會把 bucket 中的數(shù)據(jù)轉(zhuǎn)移到新的 bucket 中。
45.?JSON 標準庫對 nil slice 和 空 slice 的處理是一致的嗎
首先 JSON 標準庫對 nil slice 和 空 slice 的處理是不一致。
通常錯誤的用法,會報數(shù)組越界的錯誤,因為只是聲明了slice,卻沒有給實例化的對象。
var?slice?[]int
slice[1]?=?0此時slice的值是nil,這種情況可以用于需要返回slice的函數(shù),當函數(shù)出現(xiàn)異常的時候,保證函數(shù)依然會有nil的返回值。
empty slice 是指slice不為nil,但是slice沒有值,slice的底層的空間是空的,此時的定義如下:
slice?:=?make([]int,0)
slice?:=?[]int{}當我們查詢或者處理一個空的列表的時候,這非常有用,它會告訴我們返回的是一個列表,但是列表內(nèi)沒有任何值。
總之,nil slice 和 empty slice是不同的東西,需要我們加以區(qū)分的。
46.Golang的內(nèi)存模型,為什么小對象多了會造成gc壓力
通常小對象過多會導致 GC 三色法消耗過多的GPU。優(yōu)化思路是,減少對象分配。
47.Data Race問題怎么解決?能不能不加鎖解決這個問題
同步訪問共享數(shù)據(jù)是處理數(shù)據(jù)競爭的一種有效的方法。
golang在 1.1 之后引入了競爭檢測機制,可以使用 go run -race 或者 go build -race來進行靜態(tài)檢測。其在內(nèi)部的實現(xiàn)是,開啟多個協(xié)程執(zhí)行同一個命令, 并且記錄下每個變量的狀態(tài)。
競爭檢測器基于C/C++的ThreadSanitizer 運行時庫,該庫在Google內(nèi)部代碼基地和Chromium找到許多錯誤。這個技術(shù)在2012年九月集成到Go中,從那時開始,它已經(jīng)在標準庫中檢測到42個競爭條件。現(xiàn)在,它已經(jīng)是我們持續(xù)構(gòu)建過程的一部分,當競爭條件出現(xiàn)時,它會繼續(xù)捕捉到這些錯誤。
競爭檢測器已經(jīng)完全集成到Go工具鏈中,僅僅添加-race標志到命令行就使用了檢測器。
$?go?test?-race?mypkg????//?測試包
$?go?run?-race?mysrc.go??//?編譯和運行程序?$?go?build?-race?mycmd?
//?構(gòu)建程序?$?go?install?-race?mypkg?//?安裝程序48.在?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}
}
49.nil interface 和 nil interface?的區(qū)別
func?main()?{
???var?data?*byte
???var?in?interface{}
???fmt.Println(data,?data?==?nil)?//??true
???fmt.Println(in,?in?==?nil)?//??true
???in?=?data
???fmt.Println(in,?in?==?nil)?//??false?//?data?值為?nil,但?in?值不為?nil
}
//?正確示例
func?main()?{
??doIt?:=?func(arg?int)?interface{}?{
??var?result?*struct{}?=?nil
??if?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:?
??}
}
50.select可以用于什么
常用語gorotine的完美退出。
golang 的 select 就是監(jiān)聽 IO 操作,當 IO 操作發(fā)生時,觸發(fā)相應(yīng)的動作
每個case語句里必須是一個IO操作,確切的說,應(yīng)該是一個面向channel的IO操作。
結(jié)尾
本文總結(jié)了常見的 Go 語法問題,且在工作和面試中都容易出錯的??梢怨┐蠹覍W習和面試用。
Golang 的開源社區(qū)在不斷的壯大,是一門值得學習的語言。我公眾號有Golang 學習的全面資料,可以領(lǐng)取,祝大家學習愉快!
··················?END?··················
點擊關(guān)注公眾號,免費領(lǐng)學習資料
????????????????????????????????????????????????????????????????????????????????????點“贊”和“在看”哦
