001 slice的切片隱藏?cái)?shù)據(jù)問題
當(dāng)你重新劃分一個(gè)slice時(shí),新的slice將引用原有slice的數(shù)組。如果你忘了這個(gè)行為的話,在你的應(yīng)用分配大量臨時(shí)的slice用于創(chuàng)建新的slice來引用原有數(shù)據(jù)的一小部分時(shí),會(huì)導(dǎo)致難以預(yù)期的內(nèi)存使用。
package main
import "fmt"
func get() []byte {
raw := make([]byte, 10000)
fmt.Println(len(raw), cap(raw), &raw[0]) //prints: 10000 10000 0xc000086000
return raw[:3]
}
func main() {
data := get()
fmt.Println(len(data), cap(data), &data[0]) //prints: 3 10000 0xc000086000
}
為了避免這個(gè)陷阱,你需要從臨時(shí)的slice中拷貝數(shù)據(jù)(而不是重新劃分slice)
package main
import "fmt"
func get() []byte {
raw := make([]byte, 10000)
fmt.Println(len(raw), cap(raw), &raw[0]) //prints: 10000 10000 0xc000090000
res := make([]byte, 3)
copy(res, raw[:3])
return res
}
func main() {
data := get()
fmt.Println(len(data), cap(data), &data[0]) //prints: 3 3 0xc000016088
}
002 slice數(shù)據(jù)“損壞”問題
package main
import (
"bytes"
"fmt"
)
func main() {
path := []byte("AAAA/BBBBBBB")
sepIndex := bytes.IndexByte(path, '/')
dir1 := path[:sepIndex]
dir2 := path[sepIndex+1:]
fmt.Println(string(dir1)) // prints: AAAA
fmt.Println(string(dir2)) // prints: BBBBBBB
dir1 = append(dir1, "suffix"...)
fmt.Println(string(dir1)) // prints: AAAAsuffix
fmt.Println(string(dir2)) // prints: uffixBB
newPath := bytes.Join([][]byte{dir1, dir2}, []byte{'/'})
fmt.Println(string(newPath)) // prints: AAAAsuffix/uffixBB
}
通過分配新的slice并拷貝需要的數(shù)據(jù),你可以修復(fù)這個(gè)問題。另一個(gè)選擇是使用完整的slice表達(dá)式。
package main
import (
"bytes"
"fmt"
)
func main() {
path := []byte("AAAA/BBBBBBB")
sepIndex := bytes.IndexByte(path, '/')
dir1 := path[:sepIndex:sepIndex] // 使用完整表達(dá)式
dir2 := path[sepIndex+1:]
fmt.Println(string(dir1)) // prints: AAAA
fmt.Println(string(dir2)) // prints: BBBBBBB
dir1 = append(dir1, "suffix"...)
fmt.Println(string(dir1)) // prints: AAAAsuffix
fmt.Println(string(dir2)) // prints: BBBBBBB
newPath := bytes.Join([][]byte{dir1, dir2}, []byte{'/'})
fmt.Println(string(newPath)) // prints: AAAAsuffix/BBBBBBB
}
003 函數(shù)傳遞slice,map,struct
我們知道切片是3個(gè)字段構(gòu)成的結(jié)構(gòu)類型,所以在函數(shù)間以值的方式傳遞的時(shí)候,占用的內(nèi)存非常小,成本很低。在傳遞復(fù)制切片的時(shí)候,其底層數(shù)組不會(huì)被復(fù)制,也不會(huì)受影響,復(fù)制只是復(fù)制的切片本身,不涉及底層數(shù)組。
func main() {
slice := []int{1, 2, 3, 4, 5}
fmt.Printf("%p\n", &slice) // 0xc420082060
modify(slice)
fmt.Println(slice) // [1 10 3 4 5]
}
func modify(slice []int) {
fmt.Printf("%p\n", &slice) // 0xc420082080
slice[1] = 10
}
仔細(xì)看,這兩個(gè)切片的地址不一樣,所以可以確認(rèn)切片在函數(shù)間傳遞是復(fù)制的。而我們修改一個(gè)索引的值后,發(fā)現(xiàn)原切片的值也被修改了,說明它們共用一個(gè)底層數(shù)組。
函數(shù)間傳遞Map是不會(huì)拷貝一個(gè)該Map的副本的,也就是說如果一個(gè)Map傳遞給一個(gè)函數(shù),該函數(shù)對(duì)這個(gè)Map做了修改,那么這個(gè)Map的所有引用,都會(huì)感知到這個(gè)修改。
func main() {
dict := map[string]int{"王五": 60, "張三": 43}
modify(dict)
fmt.Println(dict["張三"]) // 10
}
func modify(dict map[string]int) {
dict["張三"] = 10
}
上面這個(gè)例子輸出的結(jié)果是10,也就是說已經(jīng)被函數(shù)給修改了,可以證明傳遞的并不是一個(gè)Map的副本。這個(gè)特性和切片是類似的,這樣就會(huì)更高,因?yàn)閺?fù)制整個(gè)Map的代價(jià)太大了。
函數(shù)傳參是值傳遞,所以對(duì)于結(jié)構(gòu)體來說也不例外,結(jié)構(gòu)體傳遞的是其本身以及里面的值的拷貝。
func main() {
jim := person{10, "Jim"}
fmt.Println(jim) // {10 Jim}
modify(jim)
fmt.Println(jim) // {10 Jim}
}
func modify(p person) {
p.age = p.age + 10
}
type person struct {
age int
name string
}
如果上面的例子我們要修改age的值可以通過傳遞結(jié)構(gòu)體的指針,我們稍微改動(dòng)下例子
func main() {
jim := person{10, "Jim"}
fmt.Println(jim) // {10 Jim}
modify(&jim)
fmt.Println(jim) // {20 Jim}
}
func modify(p *person) {
p.age = p.age + 10
}
type person struct {
age int
name string
}
非常明顯的,age的值已經(jīng)被改變。如果結(jié)構(gòu)體里有引用類型的值,比如map,那么我們即使傳遞的是結(jié)構(gòu)體的值副本,如果修改這個(gè)map的話,原結(jié)構(gòu)的對(duì)應(yīng)的map值也會(huì)被修改。
003 map初始化問題
// 未初始化的map可以取值(零值),但不能賦值
var m map[string]map[string]float64
score := m["a"]["ac"]
fmt.Println(score) // 0
//m["a"]["b"] = 34 // panic
var m1 map[string]int
it := m1["s"]
fmt.Println(it) // 0
//m1["b"] = 5 // panic
004 interface轉(zhuǎn)換問題
package main
import (
"encoding/json"
"fmt"
)
func main() {
// 由字符串unmarshal得到的結(jié)構(gòu)
a := `{
"width": "200",
"height": "200",
"img_url": "http://v2.addnewer.com/media/2019/03/1551843852785.jpeg",
"title": "舒膚佳-長效抑菌",
"source": "舒膚佳",
"origin_price": "11",
"discount_price": "10.80",
"lp_url": "-1",
"open_url": -1
}`
var cd cardMeta
err := json.Unmarshal([]byte(a), &cd)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(cd)
if v, ok := cd.LpUrl.(string); ok {
fmt.Println(v) // -1
}
if v, ok := cd.OpenUrl.(float64); ok { // 數(shù)值型的要使用float64去嘗試轉(zhuǎn)換
fmt.Println(v) // -1
}
// 自然生成的結(jié)構(gòu)
var cd2 cardMeta
var e, f int
e = 3
f = 4
cd2.LpUrl = e
cd2.OpenUrl = 6.5 // 默認(rèn)為float64類型
// d := cd2.LpUrl + f 不可以,需要先把cd2.LpUrl轉(zhuǎn)換
if v, ok := cd2.LpUrl.(int); ok {
fmt.Println(v + f) // 7
}
if v, ok := cd2.OpenUrl.(float64); ok { // 使用float32轉(zhuǎn)換不行
fmt.Println(v) // 6.5
}
var ext float32
ext = 6.5
cd2.Ext = ext
if v, ok := cd2.Ext.(float32); ok { // 使用float64轉(zhuǎn)換不行
fmt.Println(v) // 6.5
}
cd2.Ext2 = 3 // 默認(rèn)為int
if v, ok := cd2.Ext2.(int); ok { // 使用int32, int64 不行
fmt.Println(v) //3
}
}
type cardMeta struct {
Width string `json:"width"`
Height string `json:"height"`
ImgUrl string `json:"img_url"`
Title string `json:"title"`
Source string `json:"source"`
OriginPrice string `json:"origin_price,omitempty"` //使用float類型去unmarshal會(huì)報(bào)錯(cuò),因?yàn)樵純?nèi)容數(shù)值加了雙引號(hào)的
DiscountPrice string `json:"discount_price,omitempty"`
LpUrl interface{} `json:"lp_url,omitempty"`
OpenUrl interface{} `json:"open_url,omitempty"`
Ext interface{} `json:"ext"`
Ext2 interface{} `json:"ext_2"`
}