35、log包中的log.Fatal和log.Panic不仅仅记录日志,还会中止程序。它不同于Logging库。
36、关闭HTTP的Response.Body
使用defer语句关闭资源时要注意nil值,在defer语句之前要进行nill值处理
以下以http包的使用为例
package main import ( "fmt" "net/http" "io/ioutil") func main() { resp, err := http.Get("https://api.ipify.org?format=json") if resp != nil { defer resp.Body.Close() // ok,即使不读取Body中的数据,即使Body是空的,也要调用close方法 } //defer resp.Body.Close() // (1)Error:在nil值判断之前使用,resp为nil时defer中的语句执行会引发空引用的panic if err != nil { fmt.Println(err) return } //defer resp.Body.Close() // (2)Error:排除了nil隐患,但是出现重定向错误时,仍然需要调用close body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println(err) return } fmt.Println(string(body))}
在Go 1.5之前resp.Body.Close()
会读取并丢失body中的数据,保证在启用keepaliva的http时能够在下一次请求时重用。
在Go 1.5之后,就需要在关闭前手动处理。
_, err = io.Copy(ioutil.Discard, resp.Body)
如果只是读取Body的部分,就很有必要在关闭Body之前做这种手动处理。例如处理json api响应时json.NewDecoder(resp.Body).Decode(&data)
就需要处理掉剩余的数据。
37、关闭HTTP连接:
(1) 可使用req.Close=true
,表示在http请求完成时关闭连接
(2) 添加Connection: close
的连接请求头。http服务端也会发送Connection: close
的响应头,http库处理响应时会关闭连接。
package main import ( "fmt" "net/http" "io/ioutil") func main() { req, err := http.NewRequest("GET","http://golang.org",nil) if err != nil { fmt.Println(err) return } req.Close = true //or do this: //req.Header.Add("Connection", "close") resp, err := http.DefaultClient.Do(req) if resp != nil { defer resp.Body.Close() } if err != nil { fmt.Println(err) return } body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println(err) return } fmt.Println(len(string(body)))}
(3)全局关闭http连接重用。
对于相同http server发送多个请求时,适合保持网络连接;
但对于短时间内向多个HTTP服务器发送一个或两个请求时,最好在每次接收到服务端响应后关闭网络链接
package main import ( "fmt" "net/http" "io/ioutil") func main() { tr := &http.Transport{DisableKeepAlives: true} client := &http.Client{Transport: tr} resp, err := client.Get("http://golang.org") if resp != nil { defer resp.Body.Close() } if err != nil { fmt.Println(err) return } fmt.Println(resp.StatusCode) body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println(err) return } fmt.Println(len(string(body)))
38、Json反序列化数字到interface{}类型的值中,默认解析为float64类型,在使用时要注意。
var data = []byte(`{"status": 200}`) var result map[string]interface{} if err := json.Unmarshal(data, &result); err != nil { fmt.Println("error:", err) return } //var status = result["status"].(int) //error: panic: interface conversion: interface is float64, not int var status = uint64(result["status"].(float64)) //ok fmt.Println("status value:",status)
(1) 使用Decoder
类型解析JSON
var data = []byte(`{"status": 200}`) var decoder = json.NewDecoder(bytes.NewReader(data))decoder.UseNumber() if err := decoder.Decode(&result); err != nil { fmt.Println("error:", err) return} var status,_ = result["status"].(json.Number).Int64() //okfmt.Println("status value:",status) var status2 uint64if err := json.Unmarshal([]byte(result["status"].(json.Number).String()), &status2); err != nil { fmt.Println("error:", err) return}
(2)使用struct结构体映射
var data = []byte(`{"status": 200}`)var result struct { Status uint64 `json:"status"`} if err := json.NewDecoder(bytes.NewReader(data)).Decode(&result); err != nil { fmt.Println("error:", err) return} fmt.Printf("result => %+v",result) //prints: result => {Status:200}
(3) 使用struct映射数字为json.RawMessage
records := [][]byte{ []byte(`{"status": 200, "tag":"one"}`), []byte(`{"status":"ok", "tag":"two"}`), } for idx, record := range records { var result struct { StatusCode uint64 StatusName string Status json.RawMessage `json:"status"` Tag string `json:"tag"` } if err := json.NewDecoder(bytes.NewReader(record)).Decode(&result); err != nil { fmt.Println("error:", err) return } var sstatus string if err := json.Unmarshal(result.Status, &sstatus); err == nil { result.StatusName = sstatus } var nstatus uint64 if err := json.Unmarshal(result.Status, &nstatus); err == nil { result.StatusCode = nstatus } fmt.Printf("[%v] result => %+v\n",idx,result) }
39、Struct、Array、Slice、Map的比较
如果struct结构体的所有字段都能够使用==
操作比较,那么结构体变量也能够使用==
比较。
但是,如果struct字段不能使用==
比较,那么结构体变量使用==
比较会导致编译错误。
同样,array只有在它的每个元素能够使用==
比较时,array变量才能够比较。
Go提供了一些用于比较不能直接使用==
比较的函数,其中最常用的是re
flect.DeepEqual()函数。
DeepEqual()函数对于nil值的slice与空元素的slice是不相等的,这点不同于bytes.Equal()函数。
如果要忽略大小写来比较包含文字数据的字节切片(byte slice),
不建议使用bytes包和strings包里的ToUpper()、ToLower()这些函数转换后再用==、byte.Equal()、bytes.Compare()等比较,
ToUpper()、ToLower()只能处理英文文字,对其它语言无效。因此建议使用strings.EqualFold()和bytes.EqualFold()
如果要比较用于验证用户数据密钥信息的字节切片时,使用reflact.DeepEqual()、bytes.Equal()、
bytes.Compare()会使应用程序遭受计时攻击(Timing Attack),可使用crypto/subtle.ConstantTimeCompare()避免泄漏时间信息。
40、从panic中恢复
recover()函数能够捕获或拦截panic,但必须在defer函数或语句中直接调用,否则无效。
package main import "fmt" func doRecover() { fmt.Println("recovered =>",recover()) //prints: recovered => <nil>} func main() { defer func() { fmt.Println("recovered:",recover()) // ok }() defer func() { doRecover() //panic is not recovered }() // recover() //doesn't do anything panic("not good") // recover() //won't be executed :) fmt.Println("ok")}
41、在slice、array、map的for range子句中修改和引用数据项
使用range获取的数据项是从集合元素的复制过来的,并非引用原始数据,但使用索引能访问原始数据。
data := []int{1,2,3}for _,v := range data { v *= 10 // original item is not changed} data2 := []int{1,2,3}for i,v := range data2 { data2[i] *= 10 // change original item} // 元素是指针类型就不一样了data3 := []*struct{num int} {{1}, {2}, {3}}for _,v := range data { v.num *= 10} fmt.Println("data:", data) //prints data: [1 2 3]fmt.Println("data:", data2) //prints data: [10 20 30]fmt.Println(data3[0],data3[1],data3[2]) //prints &{10} &{20} &{30}
42、Slice中的隐藏数据
从一个slice上再生成一个切片slice,新的slice将直接引用原始slice的那个数组,两个slice对同一数组的操作,会相互影响。
可通过为新切片slice重新分配空间,从slice中copy部分的数据来避免相互之间的影响。
raw := make([]byte,10000)fmt.Println(len(raw),cap(raw),&raw[0]) //prints: 10000 10000 <byte_addr_x> data := raw[:3]fmt.Println(len(data),cap(data),&data[0]) //prints: 3 10000 <byte_addr_x> res := make([]byte,3)copy(res,raw[:3])fmt.Println(len(res),cap(res),&res[0]) //prints: 3 3 <byte_addr_y>
43、Slice超范围数据覆盖
从已存在的切片slice中继续切片时,新切片的capicity等于原capicity减去新切片之前部分的数量,新切片与原切片都指向同一数组空间。
新生成切片之间capicity区域是重叠的,因此在添加数据时易造成数据覆盖问题。
slice使用append添加的内容时超出capicity时,会重新分配空间。
利用这一点,将要修改的切片指定capicity为切片当前length,可避免切片之间的超范围覆盖影响。
44、Slice增加元素重新分配内存导致的怪事
slice在添加元素前,与其它切片共享同一数据区域,修改会相互影响;但添加元素导致内存重新分配之后,不再指向原来的数据区域,修改元素,不再影响其它切片。
s1 := []int{1,2,3} fmt.Println(len(s1),cap(s1),s1) //prints 3 3 [1 2 3] s2 := s1[1:] fmt.Println(len(s2),cap(s2),s2) //prints 2 2 [2 3] for i := range s2 { s2[i] += 20 } //still referencing the same array fmt.Println(s1) //prints [1 22 23] fmt.Println(s2) //prints [22 23] s2 = append(s2,4) for i := range s2 { s2[i] += 10 } //s1 is now "stale" fmt.Println(s1) //prints [1 22 23] fmt.Println(s2) //prints [32 33 14]
45、类型重定义与方法继承
从一个已存在的(non-interface)非接口类型重新定义一个新类型时,不会继承原类型的任何方法。
可以通过定义一个组合匿名变量的类型,来实现对此匿名变量类型的继承。
但是从一个已存在接口重新定义一个新接口时,新接口会继承原接口所有方法。
46、从”for switch”和”for select”代码块中跳出。
无label的break只会跳出最内层的switch
/select
代码块。
如需要从switch
/select
代码块中跳出外层的for循环,可以在for循环外部定义label,供break跳出。
return
当然也是可以的,如果在这里可以用的话。
47、在for语句的闭包中使用迭代变量会有问题
在for迭代过程中,迭代变量会一直保留,只是每次迭代值不一样。
因此在for循环中在闭包里直接引用迭代变量,在执行时直接取迭代变量的值,而不是闭包所在迭代的变量值。
如果闭包要取所在迭代变量的值,就需要for中定义一个变量来保存所在迭代的值,或者通过闭包函数传参。
package main import ( "fmt" "time") func forState1(){ data := []string{"one","two","three"} for _,v := range data { go func() { fmt.Println(v) }() } time.Sleep(3 * time.Second) //goroutines print: three, three, three for _,v := range data { vcopy := v // 使用临时变量 go func() { fmt.Println(vcopy) }() } time.Sleep(3 * time.Second) //goroutines print: one, two, three for _,v := range data { go func(in string) { fmt.Println(in) }(v) } time.Sleep(3 * time.Second) //goroutines print: one, two, three} func main() { forState1()}
再看一个坑埋得比较深的例子。
package main import ( "fmt" "time") type field struct { name string} func (p *field) print() { fmt.Println(p.name)} func main() { data := []field{{"one"},{"two"},{"three"}} for _,v := range data { // 解决办法:添加如下语句 // v := v go v.print() } time.Sleep(3 * time.Second) //goroutines print: three, three, three data2 := []*field{{"one"}, {"two"}, {"three"}} // 注意data2是指针数组 for _, v := range data2 { go v.print() // go执行是函数,函数执行之前,函数的接受对象已经传过来 } time.Sleep(3 * time.Second) //goroutines print: one, two, three}
48、defer函数调用参数
defer
后面必须是函数或方法的调用语句。defer
后面不论是函数还是方法,输入参数的值是在defer
声明时已计算好,
而不是调用开始计算。
要特别注意的是,defer
后面是方法调用语句时,方法的接受者是在defer语句执行时传递的,而不是defer声明时传入的。
type field struct{ num int}func(t *field) print(n int){ fmt.println(t.num, n)}func main() { var i int = 1 defer fmt.Println("result2 =>",func() int { return i * 2 }()) i++ v := field{1} defer v.print(func() int { return i * 2 }()) v = field{2} i++ // prints: // 2 4 // result => 2 (not ok if you expected 4)}
49、defer
语句调用是在当前函数结束之后调用,而不是变量的作用范围。
for _,target := range targets { f, err := os.Open(target) if err != nil { fmt.Println("bad target:",target,"error:",err) //prints error: too many open files break } defer f.Close() //will not be closed at the end of this code block, but closed at end of this function // 解决方法1:不用defer // f.Close() // 解决方法2:将for中的语句添加到匿名函数中执行。 // func () { // }() }
50、失败的类型断言
var.(T)
类型断言失败时会返回T
类型的“0值”,而不是变量原始值。
var data interface{} = "great" if data, ok := data.(int); ok { fmt.Println("[is an int] value =>",data) } else { fmt.Println("[not an int] value =>",data) //prints: [not an int] value => 0 (not "great") } if res, ok := data.(int); ok { fmt.Println("[is an int] value =>",res) } else { fmt.Println("[not an int] value =>",data) //prints: [not an int] value => great (as expected) }