go基礎類
1. go優勢
* 天生支援併發,效能高
* 單一的標準程式碼格式,比其它語言更具可讀性
* 自動垃圾收集比java和python更有效,因為它與程式同時執行
go資料型別
int string float bool array slice map channel pointer struct interface method
go程式中的包是什麼
* 專案中包含go原始檔以及其它包的目錄,原始檔中的函式、變數、型別都儲存在該包中
* 每個原始檔都屬於一個包,該包在檔案頂部使用 package packageName 宣告
* 我們在原始檔中需要匯入第三方包時需要使用 import packageName
go支援什麼形式的型別轉換?將整數轉換為浮點數
* go支援顯示型別轉換,以滿足嚴格的型別要
* a := 15
* b := float64(a)
* fmt.Println(b, reflect.TypeOf(b))
什麼是 goroutine,你如何停止它?
* goroutine是協程/輕量級執行緒/使用者態執行緒,不同於傳統的核心態執行緒
* 佔用資源特別少,建立和銷燬只在使用者態執行不會到核心態,節省時間
* 建立goroutine需要使用go關鍵字
* 可以向goroutine傳送一個訊號通道來停止它,goroutine內部需要檢查訊號通道
例子:
```
func main() {
var wg sync.WaitGroup
var exit = make(chan bool)
wg.Add(1)
go func() {
for {
select {
case <-exit: // 接收到訊號後return退出當前goroutine
fmt.Println("goroutine接收到訊號退出了!")
wg.Done()
return
default:
fmt.Println("還沒有接收到訊號")
}
}
}()
exit <- true
wg.Wait()
}
```
如何在執行時檢查變數型別
* 型別開關(Type Switch)是在執行時檢查變數型別的最佳方式。
* 型別開關按型別而不是值來評估變數。每個 Switch 至少包含一個 case 用作條件語句
* 如果沒有一個 case 為真,則執行 default。
go兩個介面之間可以存在什麼關係
* 如果兩個介面有相同的方法列表,那麼他倆就是等價的,可以相互賦值
* 介面A可以巢狀到介面B裡面,那麼介面B就有了自己的方法列表+介面A的方法列表
go中同步鎖(互斥鎖)有什麼特點,作用是什麼?何時使用互斥鎖,何時使用讀寫鎖?
* 當一個goroutine獲得了Mutex後,其它goroutine就只能乖乖等待,除非該goroutine釋放Mutex
* RWMutext在讀鎖佔用的情況下會阻止寫,但不會阻止讀,在寫鎖佔用的情況下,會阻止任何其它goroutine進來
* 無論是讀還是寫,整個鎖相當於由該goroutine獨佔
* 作用:保證資源在使用時的獨有性,不會因為併發導致資料錯亂,保證系統穩定性
* 案例:
```
package main
import (
"fmt"
"sync"
"time"
)
var (
num = 0
lock = sync.RWMutex{} // 耗時:100+毫秒
//lock = sync.Mutex{} // 耗時:50+毫秒
)
func main() {
start := time.Now()
go func() {
for i := 0; i < 100000; i++{
lock.Lock()
//fmt.Println(num)
num++
lock.Unlock()
}
}()
for i := 0; i < 100000; i++{
lock.Lock()
//fmt.Println(num)
num++
lock.Unlock()
}
fmt.Println(num)
fmt.Println(time.Now().Sub(start))
}
```
// 結論:
// 1. 如果對資料寫的比較多,使用Mutex同步鎖/互斥鎖效能更高
// 2. 如果對資料讀的比較多,使用RWMutex讀寫鎖效能更高
goroutine案例(兩個goroutine,一個負責輸出數字,另一個負責輸出26個英文字母,格式如下:12ab34cd56ef78gh ... yz)
package main
import (
"fmt"
"sync"
"unicode/utf8"
)
// 案例:兩個goroutine,一個負責輸出數字,另一個負責輸出26個英文字母,格式如下:12ab34cd56ef78gh ... yz
var (
wg = sync.WaitGroup{}
chNum = make(chan bool)
chAlpha = make(chan bool)
)
func main() {
go func() {
i := 1
for {
<-chNum
fmt.Printf("%v%v", i, i + 1)
i += 2
chAlpha <- true
}
}()
wg.Add(1)
go func() {
str := "abcdefghigklmnopqrstuvwxyz"
i := 0
for {
<-chAlpha
fmt.Printf("%v", str[i:i+2])
i += 2
if i >= utf8.RuneCountInString(str){
wg.Done()
return
}
chNum <- true
}
}()
chNum <- true
wg.Wait()
}
go語言中,channel通道有什麼特點,需要注意什麼?
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
var ch chan int
var ch1 = make(chan int)
fmt.Println(ch, ch1) // <nil> 0xc000086060
wg.Add(1)
go func() {
//ch <- 15 // 如果給一個nil的channel傳送資料會造成永久阻塞
//<-ch // 如果從一個nil的channel中接收資料也會造成永久阻塞
ret := <-ch1
fmt.Println(ret)
ret = <-ch1 // 從一個已關閉的通道中接收資料,如果緩衝區中為空,則返回該型別的零值
fmt.Println(ret)
wg.Done()
}()
go func() {
//close(ch1)
ch1 <- 15 // 給一個已關閉通道傳送資料就會包panic錯誤
close(ch1)
}()
wg.Wait()
}
- 結論:
- 給一個nil channel傳送資料時會一直堵塞
- 從一個nil channel接收資料時會一直阻塞
- 給一個已關閉的channel傳送資料時會panic
- 從一個已關閉的channel中讀取資料時,如果channel為空,則返回通道中型別的領零值
go中channel緩衝有什麼特點?
go中的cap函式可以作用於哪些內容?
- 可作用於的型別有
- 陣列(array)
- 切片(slice)
- 通道(channel)
go convey是什麼,一般用來做什麼?
- go convey是一個支援golang的單元測試框架
- 能夠自動監控檔案修改並啟動測試,並可以將測試結果實時輸出到web介面
- 提供了豐富的斷言簡化測試用例的編寫
go語言中new的作用是什麼?
- 使用new函式來分配記憶體空間
- 傳遞給new函式的是一個型別,而不是一個值
- 返回值是指向這個新分配的地址的指標
go語言中的make作用是什麼?
- 分配記憶體空間並進行初始化, 返回值是該型別的例項而不是指標
- make只能接收三種型別當做引數:slice、map、channel
總結new和make的區別?
- new可以接收任意內建型別當做引數,返回的是對應型別的指標
- make只能接收slice、map、channel當做引數,返回值是對應型別的例項
Printf、Sprintf、FprintF都是格式化輸出,有什麼不同?
- 雖然這三個函式都是格式化輸出,但是輸出的目標不一樣
- Printf輸出到控制檯
- Sprintf結果賦值給返回值
- FprintF輸出到指定的io.Writer介面中
例如:
func main() {
var a int = 15
file, _ := os.OpenFile("test.log", os.O_CREATE|os.O_APPEND, 0644)
// 格式化字串並輸出到檔案
n, _ := fmt.Fprintf(file, "%T:%v:%p", a, a, &a)
fmt.Println(n)
}
go語言中的陣列和切片的區別是什麼?
- 陣列:
- 陣列固定長度,陣列長度是陣列型別的一部分,所以[3]int和[4]int是兩種不同的陣列型別
- 陣列型別需要指定大小,不指定也會根據初始化,自動推算出大小,大小不可改變,陣列是通過值傳遞的
- 切片:
- 切片的長度可改變,切片是輕量級的資料結構,三個屬性:指標、長度、容量
- 不要指定切片的大小,切片也是值傳遞只不過切片的一個屬性指標指向的資料不變,所以看起來像引用傳遞
- 切片可以通過陣列來初始化也可以通過make函式來初始化,初始化時的len和cap相等,然後進行擴容
- 切片擴容的時候會導致底層的陣列複製,也就是切片中的指標屬性會發生變化
- 切片也是拷貝,在不發生擴容時,底層使用的是同一個陣列,當對其中一個切片append的時候, 該切片長度會增加
但是不會影響另外一個切片的長度
- copy函式將原切片拷貝到目標切片,會導致底層陣列複製,因為目標切片需要通過make函式來宣告初始化記憶體,然後
將原切片指向的陣列元素拷貝到新切片指向的陣列元素
- 重點:陣列儲存真正的資料,切片值儲存陣列的指標和該切片的長度和容量
- append函式如果切片容量足夠的話,只會影響當前切片的長度,陣列底層不會複製,不會影響與陣列關聯的其它切片的長度
- copy直接會導致陣列底層複製
go語言中值傳遞和地址傳遞(引用傳遞)如何執行?有什麼區別?舉例說明
- 值傳遞會把引數的值複製一份放到對應的函式裡,兩個變數的地址不同,不可互相修改
- 地址傳遞會把引數的地址複製一份放到對應的函式裡,兩個變數的地址相同,可以互相修改
- 例如:陣列傳遞就是值傳遞,而切片傳遞就是陣列的地址傳遞(本質上切片值傳遞,只不過是儲存的資料地址相同)
go中陣列和切片在傳遞時有什麼區別?
- 陣列是值傳遞
- 切片地址傳遞(引用傳遞)
go中是如何實現切片擴容的?
- 當容量小於1024時,每次擴容容量翻倍,當容量大於1024時,每次擴容加25%
func main() {
s1 := make([]int, 0)
for i := 0; i < 3000; i++{
fmt.Println("len =", len(s1), "cap = ", cap(s1))
s1 = append(s1, i)
}
}
看下面程式碼defer的執行順序是什麼?defer的作用和特點是什麼?
- 在普通函式或方法前加上defer關鍵字,就完成了defer所需要的語法,當defer語句被執行時,跟在defer語句後的函式會被延遲執行
- 知道包含該defer語句的函式執行完畢,defer語句後的函式才會執行,無論包含defer語句的函式是通過return正常結束,還是通過panic導致的異常結束
- 可以在一個函式中執行多條defer語句,由於在棧中儲存,所以它的執行順序和宣告順序相反
defer語句中通過recover捕獲panic例子
func main() {
defer func() {
err := recover()
fmt.Println(err)
}()
defer fmt.Println("first defer")
defer fmt.Println("second defer")
defer fmt.Println("third defer")
fmt.Println("哈哈哈哈")
panic("abc is an error")
}
go中的25個關鍵字
- 程式宣告2
package import
- 程式實體宣告和定義8
var const type func struct map chan interface
- 程式流程控制15
for range continue break select switch case default if else fallthrough defer go goto return
寫一個定時任務,每秒執行一次
func main() {
t1 := time.NewTicker(time.Second * 1)
var i = 1
for {
if i == 10{
break
}
select {
case <-t1.C: // 一秒執行一次的定時任務
task1(i)
i++
}
}
}
func task1(i int) {
fmt.Println("task1執行了---", i)
}
switch case fallthrough default使用場景
func main() {
var a int
for i := 0; i < 10; i++{
a = rand.Intn(100)
switch {
case a >= 80:
fmt.Println("優秀", a)
fallthrough
case a >= 60:
fmt.Println("及格", a)
fallthrough
default:
fmt.Println("不及格", a)
}
}
}
defer的常用場景
- defer語句經常被用於處理成對的操作開啟/關閉,連結/斷開連線,加鎖/釋放鎖
- 通過defer機制,不論函式邏輯多複雜,都能保證在任何執行路徑下,資源被釋放
- 釋放資源的defer語句應該直接跟在請求資源處理錯誤之後
- 注意:defer一定要放在請求資源處理錯誤之後
go中slice的底層實現
- 切片是基於陣列實現的,它的底層是陣列,它本身非常小,它可以理解為對底層陣列的抽閒
- 因為基於陣列實現,所以它的底層記憶體是連續分配的,效率非常高,還可以通過索引獲取資料
- 切片本身並不是動態陣列或陣列指標,它內部實現的資料結構體通過指標引用底層陣列
- 設定相關屬性將讀寫操作限定在指定的區域內,切片本身是一個只讀物件,其工作機制類似於陣列指標的一種封裝
- 切片物件非常小,因為它只有三個欄位的資料結構:指向底層陣列的指標、切片的長度、切片的容量
go中slice的擴容機制,有什麼注意點?
- 首先判斷,如果新申請的容量大於2倍的舊容量,最終容量就是新申請的容量
- 否則判斷,如果舊切片的長度小於1024,最終容量就是舊容量的兩倍
- 否則判斷,如果舊切片的長度大於等於1024,則最終容量從舊容量開始迴圈增加原來的1/4,直到最終容量大於新申請的容量
- 如果最終容量計算值溢位,則最終容量就是新申請的容量
擴容前後的slice是否相同?
- 情況一:
- 原來陣列還有容量可以擴容(實際容量沒有填充完),這種情況下,擴容之後的切片還是指向原來的陣列
- 對一個切片的操作可能影響多個指標指向相同地址的切片
- 情況二:
- 原來陣列的容量已經達到了最大值,在擴容,go預設會先開闢一塊記憶體區域,把原來的值拷貝過來
- 然後再執行append操作,這種情況絲毫不影響原陣列
- 注意:要複製一個slice最好使用copy函式
go中的引數傳遞、引用傳遞
- go語言中的所有的傳參都是值傳遞(傳值),都是一個副本,一個拷貝,
- 因為拷貝的內容有時候是非引用型別(int, string, struct)等,這樣在函式中就無法修改原內容資料
- 有的是引用型別(指標、slice、map、chan),這樣就可以修改原內容資料
- go中的引用型別包含slice、map、chan,它們有複雜的內部結構,除了申請記憶體外,還需要初始化相關屬性
- 內建函式new計算型別大小,為其分配零值記憶體,返回指標。
- 而make會被編譯器翻譯成具體的建立函式,由其分配記憶體並初始化成員結構,返回物件而非指標
雜湊概念講解
- 雜湊表又稱為雜湊表,由一個直接定址表和一個雜湊函式組成
- 由於雜湊表的大小是有限的而要儲存的數值是無限的,因此對於任何雜湊函式,
- 都會出現兩個不同元素對映到相同位置的情況,這種情況叫做雜湊衝突
- 通過拉鍊法解決雜湊衝突:
* 雜湊表每個位置都連線一個連結串列,當衝突發生是,衝突的元素將會被加到該位置連結串列的最後
- 雜湊表的查詢速度起決定性作用的就是雜湊函式: 除法雜湊發、乘法雜湊法、全域雜湊法
- 雜湊表的應用?
- 字典與集合都是通過雜湊表來實現的
- md5曾經是密碼學中常用的雜湊函式,可以吧任意長度的資料對映為128位的雜湊值
go中的map底層實現
- go中map的底層實現就是一個雜湊表,因此實現map的過程實際上就是實現雜湊表的過程
- 在這個雜湊表中,主要出現的結構體由兩個,一個是hmap、一個是bmap
- go中也有一個雜湊函式,用來對map中的鍵生成雜湊值
- hash結果的低位用於把k/v放到bmap陣列中的哪個bmap中
- 高位用於key的快速預覽,快速試錯
go中的map如何擴容
- 翻倍擴容:如果map中的鍵值對個數/桶的個數>6.5,就會引發翻倍擴容
- 等量擴容:當B<=15時,如果溢位桶的個數>=2的B次方就會引發等量擴容
- 當B>15時,如果溢位桶的個數>=2的15次方時就會引發等量擴容
go中map的查詢
- go中的map採用的是雜湊查詢表,由雜湊函式通過key和雜湊因此計算出雜湊值,
- 根據hamp中的B來確定放到哪個桶中,如果B=5,那麼就根據雜湊值的後5位確定放到哪個桶中
- 在用雜湊值的高8位確定桶中的位置,如果當前的bmap中未找到,則去對應的overflow bucket中查詢
- 如果當前map處於資料搬遷狀態,則優先從oldbuckets中查詢
介紹一下channel
- go中不要通過共享記憶體來通訊,而要通過通訊實現共享記憶體
- go中的csp併發模型,中文名通訊順序程式,就是通過goroutine和channel實現的
- channel收發遵循先進先出,分為有緩衝通道(非同步通道),無緩衝通道(同步通道)
go中channel的特性
- 給一個nil的channel傳送資料,會造成永久阻塞
- 從一個nil的channel接收資料,會造成永久阻塞
- 給一個已經關閉的channel傳送資料,會造成panic
- 從一個已經關閉的channel接收資料,如果緩衝區為空,會返回零值
- 無緩衝的channel是同步的,有緩衝的channel是非同步的
- 關閉一個nil channel會造成panic
channel中ring buffer的實現
- channel中使用了ring buffer(環形緩衝區)來快取寫入資料,
- ring buffer有很多好處,而且非常適合實現FiFo的固定長度佇列
- channel中包含buffer、sendx、recvx
- recvx指向最早被讀取的位置,sendx指向再次寫入時插入的位置