《Go入門指南》筆記
Map 刪除:delete(map1, "Washington")
假設(shè)我們想獲取一個(gè) map 類型的切片装畅,我們必須使用兩次 make()
函數(shù),第一次分配切片盒齿,第二次分配 切片中每個(gè) map 元素
container:list/ring
runtime
-
reflect
: 實(shí)現(xiàn)通過程序運(yùn)行時(shí)反射,讓程序操作任意類型的變量。 -
runtime
: Go 程序運(yùn)行時(shí)的交互操作接谨,例如垃圾回收和協(xié)程創(chuàng)建。 - regex: 正則
- sync.Mutex:加鎖
- godoc -http=:6060 -goroot="." 可以查看文檔
如何強(qiáng)制使用工廠方法塘匣?
Go 語言不支持面向?qū)ο缶幊陶Z言中那樣的構(gòu)造子方法脓豪,但是可以很容易的在 Go 中實(shí)現(xiàn) “構(gòu)造子工廠” 方法。為了方便通常會(huì)為類型定義一個(gè)工廠忌卤,按慣例扫夜,工廠的名字以 new 或 New 開頭。
將對(duì)象聲明稱私有(即小寫開頭)驰徊,調(diào)用Newxx()方法笤闯。
// 注意為私有方法
type matrix struct {
...
}
func NewMatrix(params) *matrix {
m := new(matrix) // 初始化 m
return m
}
匿名字段和內(nèi)嵌結(jié)構(gòu)體?
當(dāng)兩個(gè)字段擁有相同的名字(可能是繼承來的名字)時(shí)該怎么辦呢棍厂?
- 外層名字會(huì)覆蓋內(nèi)層名字(但是兩者的內(nèi)存空間都保留)颗味,這提供了一種重載字段或方法的方式;
- 如果相同的名字在同一級(jí)別出現(xiàn)了兩次牺弹,如果這個(gè)名字被程序使用了浦马,將會(huì)引發(fā)一個(gè)錯(cuò)誤(不使用沒關(guān)系)时呀。沒有辦法來解決這種問題引起的二義性,必須由程序員自己修正晶默。
在一個(gè)對(duì)象 obj 被從內(nèi)存移除前執(zhí)行一些特殊操作退唠?
如果需要在一個(gè)對(duì)象 obj 被從內(nèi)存移除前執(zhí)行一些特殊操作,比如寫到日志文件中荤胁,可以通過如下方式調(diào)用函數(shù)來實(shí)現(xiàn):
runtime.SetFinalizer(obj, func(obj *typeObj))
func(obj *typeObj)
需要一個(gè) typeObj
類型的指針參數(shù) obj
瞧预,特殊操作會(huì)在它上面執(zhí)行。func
也可以是一個(gè)匿名函數(shù)仅政。
在對(duì)象被 GC 進(jìn)程選中并從內(nèi)存中移除以前垢油,SetFinalizer
都不會(huì)執(zhí)行,即使程序正常結(jié)束或者發(fā)生錯(cuò)誤
接口命名約定圆丹?
(按照約定滩愁,只包含一個(gè)方法的)接口的名字由方法名加 [e]r
后綴組成,例如 Printer
辫封、Reader
硝枉、Writer
、Logger
倦微、Converter
等等妻味。還有一些不常用的方式(當(dāng)后綴 er
不合適時(shí)),比如 Recoverable
欣福,此時(shí)接口名以 able
結(jié)尾责球,或者以 I
開頭(像 .NET
或 Java
中那樣)
一個(gè)接口可以包含一個(gè)或多個(gè)其他的接口,這相當(dāng)于直接將這些內(nèi)嵌接口的方法列舉在外層接口中一樣拓劝。
接口類型判斷?
Type-switch
func classifier(items ...interface{}) {
for i, x := range items {
switch x.(type) {
case bool:
fmt.Printf("Param #%d is a bool\n", i)
case float64:
fmt.Printf("Param #%d is a float64\n", i)
case int, int64:
fmt.Printf("Param #%d is a int\n", i)
case nil:
fmt.Printf("Param #%d is a nil\n", i)
case string:
fmt.Printf("Param #%d is a string\n", i)
default:
fmt.Printf("Param #%d is unknown\n", i)
}
}
}
如何使用接口來獲取通用型雏逾?
func Sort(data Sorter) {
for pass := 1; pass < data.Len(); pass++ {
for i := 0;i < data.Len() - pass; i++ {
if data.Less(i+1, i) {
data.Swap(i, i + 1)
}
}
}
}
type Sorter interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
現(xiàn)在如果我們想對(duì)一個(gè) int
數(shù)組進(jìn)行排序,所有必須做的事情就是:為數(shù)組定一個(gè)類型并在它上面實(shí)現(xiàn) Sorter
接口的方法:
type IntArray []int
func (p IntArray) Len() int { return len(p) }
func (p IntArray) Less(i, j int) bool { return p[i] < p[j] }
func (p IntArray) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
data := []int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586}
a := sort.IntArray(data) //conversion to type IntArray from package sort
sort.Sort(a)
切片注意事項(xiàng)郑临?
復(fù)制數(shù)據(jù)切片至空接口切片栖博,必須要顯式復(fù)制
切片的底層指向一個(gè)數(shù)組,該數(shù)組的實(shí)際容量可能要大于切片所定義的容量厢洞。只有在沒有任何切片指向的時(shí)候仇让,底層的數(shù)組內(nèi)存才會(huì)被釋放,這種特性有時(shí)會(huì)導(dǎo)致程序占用多余的內(nèi)存犀变。
Map注意事項(xiàng)妹孙?
Map使用make進(jìn)行初始化 不要使用new;假設(shè)我們想獲取一個(gè) map 類型的切片获枝,我們必須使用兩次 make()
函數(shù)蠢正,第一次分配切片,第二次分配 切片中每個(gè) map 元素
判斷key是否存在 : val1, isPresent = map1[key1]
map中刪除key: delete(map1, key1)
Map排序:如果你想為 map 排序省店,需要將 key(或者 value)拷貝到一個(gè)切片嚣崭,再對(duì)切片排序(使用 sort 包)笨触,然后可以使用切片的 for-range 方法打印出所有的 key 和 value
方法相關(guān)小技巧?
在 Go 中雹舀,類型就是類(數(shù)據(jù)和關(guān)聯(lián)的方法)芦劣。Go 不知道類似面向?qū)ο笳Z言的類繼承的概念。繼承有兩個(gè)好處:代碼復(fù)用和多態(tài)说榆。
在 Go 中虚吟,代碼復(fù)用通過組合和委托實(shí)現(xiàn),多態(tài)通過接口的使用來實(shí)現(xiàn):有時(shí)這也叫 組件編程(Component Programming)签财。
許多開發(fā)者說相比于類繼承串慰,Go 的接口提供了更強(qiáng)大、卻更簡單的多態(tài)行為唱蒸。
讀寫數(shù)據(jù)邦鲫?
如何讀寫一個(gè)文件?
從命令行讀入數(shù)據(jù)神汹?
文件拷貝 io.Copy()
從命令行讀取參數(shù) : os.Args[1:]
os.OpenFile outputWriter := bufio.NewWriter(outputFile)
https://learnku.com/docs/the-way-to-go/122-file-reading-and-writing/3662
錯(cuò)誤處理庆捺?
永遠(yuǎn)不要忽略錯(cuò)誤,否則可能會(huì)導(dǎo)致程序崩潰Fㄎ骸滔以!
創(chuàng)建error: fmt.Errorf() / errors.New()
當(dāng)發(fā)生像數(shù)組下標(biāo)越界或類型斷言失敗這樣的運(yùn)行錯(cuò)誤時(shí),Go 運(yùn)行時(shí)會(huì)觸發(fā)運(yùn)行時(shí) panic蚁堤,伴隨著程序的崩潰拋出一個(gè) runtime.Error
接口類型的值醉者。這個(gè)錯(cuò)誤值有個(gè) RuntimeError()
方法用于區(qū)別普通錯(cuò)誤。
panic 會(huì)導(dǎo)致棧被展開直到 defer 修飾的 recover () 被調(diào)用或者程序中止披诗。
package main
import (
"fmt"
)
func badCall() {
panic("bad end")
}
func test() {
defer func() {
if e := recover(); e != nil {
fmt.Printf("Panicing %s\r\n", e)
}
}()
badCall()
fmt.Printf("After bad call\r\n") // <-- wordt niet bereikt
}
func main() {
fmt.Printf("Calling test\r\n")
test()
fmt.Printf("Test completed\r\n")
}
Calling test
Panicing bad end
Test completed
自定義包中的錯(cuò)誤處理和 panicking?
這是所有自定義包實(shí)現(xiàn)者應(yīng)該遵守的最佳實(shí)踐:
1)在包內(nèi)部,總是應(yīng)該從 panic 中 recover:不允許顯式的超出包范圍的 panic ()
2)向包的調(diào)用者返回錯(cuò)誤值(而不是 panic)立磁。
在包內(nèi)部呈队,特別是在非導(dǎo)出函數(shù)中有很深層次的嵌套調(diào)用時(shí),對(duì)主調(diào)函數(shù)來說用 panic 來表示應(yīng)該被翻譯成錯(cuò)誤的錯(cuò)誤場景是很有用的(并且提高了代碼可讀性)唱歧。
包內(nèi)panic宪摧;包外error
// parse.go
package parse
import (
"fmt"
"strings"
"strconv"
)
// A ParseError indicates an error in converting a word into an integer.
type ParseError struct {
Index int // The index into the space-separated list of words.
Word string // The word that generated the parse error.
Err error // The raw error that precipitated this error, if any.
}
// String returns a human-readable error message.
func (e *ParseError) String() string {
return fmt.Sprintf("pkg parse: error parsing %q as int", e.Word)
}
// Parse parses the space-separated words in in put as integers.
func Parse(input string) (numbers []int, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("pkg: %v", r)
}
}
}()
fields := strings.Fields(input)
numbers = fields2numbers(fields)
return
}
func fields2numbers(fields []string) (numbers []int) {
if len(fields) == 0 {
panic("no words to parse")
}
for idx, field := range fields {
num, err := strconv.Atoi(field)
if err != nil {
panic(&ParseError{idx, field, err})
}
numbers = append(numbers, num)
}
return
}
使用一種用閉包處理錯(cuò)誤的模式?
func check(err error) { if err != nil { panic(err) } }
// errorhandler:這是一個(gè)包裝函數(shù)颅崩。接收一個(gè) fType1 類型的函數(shù) fn 并返回一個(gè)調(diào)用 fn 的函數(shù)几于。里面就包含有 defer/recover 機(jī)制
func errorHandler(fn fType1) fType1 {
return func(a type1, b type2) {
defer func() {
if err, ok := recover().(error); ok {
log.Printf(“run time panic: %v”, err)
}
}()
fn(a, b)
}
}
如何啟動(dòng)外部命令和程序?
os 包有一個(gè) StartProcess
函數(shù)可以調(diào)用或啟動(dòng)外部系統(tǒng)命令和二進(jìn)制可執(zhí)行文件沿后;它的第一個(gè)參數(shù)是要運(yùn)行的進(jìn)程沿彭,第二個(gè)參數(shù)用來傳遞選項(xiàng)或參數(shù),第三個(gè)參數(shù)是含有系統(tǒng)環(huán)境基本信息的結(jié)構(gòu)體尖滚。
這個(gè)函數(shù)返回被啟動(dòng)進(jìn)程的 id(pid)喉刘,或者啟動(dòng)失敗返回錯(cuò)誤瞧柔。
exec 包中也有同樣功能的更簡單的結(jié)構(gòu)體和函數(shù);主要是 exec.Command(name string, arg ...string)
和 Run()
睦裳。首先需要用系統(tǒng)命令或可執(zhí)行文件的名字創(chuàng)建一個(gè) Command
對(duì)象造锅,然后用這個(gè)對(duì)象作為接收者調(diào)用 Run()
單元測(cè)試和基準(zhǔn)測(cè)試?
寫測(cè)試用例
不要通過共享內(nèi)存來通信廉邑,而通過通信來共享內(nèi)存
xxx
通道channel
一個(gè)無緩沖通道只能包含 1 個(gè)元素哥蔚,有時(shí)顯得很局限。我們給通道提供了一個(gè)緩存蛛蒙,可以在擴(kuò)展的 make
命令中設(shè)置它的容量
func compute(ch chan int){
ch <- someComputation() // when it completes, signal on the channel.
}
func main(){
ch := make(chan int) // allocate a channel.
go compute(ch) // stat something in a goroutines
doSomethingElseForAWhile()
result := <- ch
}
https://learnku.com/docs/the-way-to-go/142-covariance-channel/3686
學(xué)習(xí)研究下素?cái)?shù)打印這個(gè)函數(shù)
后臺(tái)服務(wù)模式:
// Backend goroutine.
func backend() {
for {
select {
case cmd := <-ch1:
// Handle ...
case cmd := <-ch2:
...
case cmd := <-chStop:
// stop server
}
}
}
協(xié)程與恢復(fù)
func server(workChan <-chan *Work) {
for work := range workChan {
go safelyDo(work) // start the goroutine for that work
}
}
func safelyDo(work *Work) {
defer func() {
if err := recover(); err != nil {
log.Printf("Work failed with %s in %v", err, work)
}
}()
do(work)
}
使用鎖還是通道肺素?
- 使用鎖的情景:
- 訪問共享數(shù)據(jù)結(jié)構(gòu)中的緩存信息
- 保存應(yīng)用程序上下文和狀態(tài)信息數(shù)據(jù)
- 使用通道的情景:
- 與異步操作的結(jié)果進(jìn)行交互
- 分發(fā)任務(wù)
- 傳遞數(shù)據(jù)所有權(quán)
惰性生成器的實(shí)現(xiàn)?
package main
import (
"fmt"
)
type Any interface{}
type EvalFunc func(Any) (Any, Any)
func main() {
evenFunc := func(state Any) (Any, Any) {
os := state.(int)
ns := os + 2
return os, ns
}
even := BuildLazyIntEvaluator(evenFunc, 0)
for i := 0; i < 10; i++ {
fmt.Printf("%vth even: %v\n", i, even())
}
}
func BuildLazyEvaluator(evalFunc EvalFunc, initState Any) func() Any {
retValChan := make(chan Any)
loopFunc := func() {
var actState Any = initState
var retVal Any
for {
retVal, actState = evalFunc(actState)
retValChan <- retVal
}
}
retFunc := func() Any {
return <- retValChan
}
go loopFunc()
return retFunc
}
func BuildLazyIntEvaluator(evalFunc EvalFunc, initState Any) func() int {
ef := BuildLazyEvaluator(evalFunc, initState)
return func() int {
return ef().(int)
}
}
使用通道實(shí)現(xiàn)Future模式宇驾。類似于java里面的future倍靡?
每一個(gè)協(xié)程執(zhí)行完返回一個(gè)channel對(duì)象,然后最后從每一個(gè)channel中讀取數(shù)據(jù)课舍。
package gorout
type Matrix struct {
}
func InverseProduct(a Matrix, b Matrix) Matrix{
a_inv_future := InverseFuture(a) // start as a goroutine
b_inv_future := InverseFuture(b) // start as a goroutine
a_inv := <-a_inv_future
b_inv := <-b_inv_future
return Product(a_inv, b_inv)
}
func InverseFuture(a Matrix) (chan Matrix){
future := make(chan Matrix)
go func() {
future <- Inverse(a)
}()
return future
}
func Inverse(a Matrix) Matrix{
return a
}
func Product(a,b Matrix) (c Matrix){
return c
}
如何設(shè)計(jì)良好的錯(cuò)誤處理塌西,避免錯(cuò)誤檢測(cè)使代碼變得混亂?
使用recover來終止panic筝尾。
使用閉包的方式解決錯(cuò)誤捡需。這樣子啊子程序中錯(cuò)誤處理簡化成一個(gè)check(); 最后包裝一層筹淫。
func check(err error) { if err != nil { panic(err) } }
func errorHandler(fn fType1) fType1 {
return func(a type1, b type2) {
defer func() {
if err, ok := recover().(error); ok {
log.Printf(“run time panic: %v”, err)
}
}()
fn(a, b)
}
}
結(jié)構(gòu)體初始化站辉?
當(dāng)結(jié)構(gòu)體的命名以大寫字母開頭時(shí),該結(jié)構(gòu)體在包外可見损姜。
通常情況下饰剥,為每個(gè)結(jié)構(gòu)體定義一個(gè)構(gòu)建函數(shù),并推薦使用構(gòu)建函數(shù)初始化結(jié)構(gòu)體
如何通過一個(gè)通道讓主程序等待直到協(xié)程完成摧阅?(信號(hào)量模式)
ch := make(chan int) // Allocate a channel.
// Start something in a goroutine; when it completes, signal on the channel.
go func() {
// doSomething
ch <- 1 // Send a signal; value does not matter.
}()
doSomethingElseForAWhile()
<-ch // Wait for goroutine to finish; discard sent value.
簡單的超時(shí)模版汰蓉?
timeout := make(chan bool, 1)
go func() {
time.Sleep(1e9) // one second
timeout <- true
}()
select {
case <-ch:
// a read from ch has occurred
case <-timeout:
// the read from ch has timed out
}
如何使用輸入通道和輸出通道代替鎖?
func Worker(in, out chan *Task) {
for {
t := <-in
process(t)
out <- t
}
}
如何取消取消耗時(shí)很長的同步調(diào)用棒卷?
ch := make(chan error, 1)
go func() { ch <- client.Call("Service.Method", args, &reply) } ()
select {
case resp := <-ch
// use resp and reply
case <-time.After(timeoutNs):
// call timed out
break
}
如何在程序出錯(cuò)時(shí)顾孽,終止程序?
if err != nil {
fmt.Printf(“Program stopping with error %v”, err)
os.Exit(1)
}
// or
if err != nil {
panic(“ERROR occurred: “ + err.Error())
}