001 slice的切片隐藏数据问题
当你重新划分一个slice时,新的slice将引用原有slice的数组。如果你忘了这个行为的话,在你的应用分配大量临时的slice用于创建新的slice来引用原有数据的一小部分时,会导致难以预期的内存使用。
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
}
为了避免这个陷阱,你需要从临时的slice中拷贝数据(而不是重新划分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数据“损坏”问题
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并拷贝需要的数据,你可以修复这个问题。另一个选择是使用完整的slice表达式。
package main
import (
"bytes"
"fmt"
)
func main() {
path := []byte("AAAA/BBBBBBB")
sepIndex := bytes.IndexByte(path, '/')
dir1 := path[:sepIndex: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: BBBBBBB
newPath := bytes.Join([][]byte{dir1, dir2}, []byte{'/'})
fmt.Println(string(newPath)) // prints: AAAAsuffix/BBBBBBB
}
003 函数传递slice,map,struct
我们知道切片是3个字段构成的结构类型,所以在函数间以值的方式传递的时候,占用的内存非常小,成本很低。在传递复制切片的时候,其底层数组不会被复制,也不会受影响,复制只是复制的切片本身,不涉及底层数组。
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
}
仔细看,这两个切片的地址不一样,所以可以确认切片在函数间传递是复制的。而我们修改一个索引的值后,发现原切片的值也被修改了,说明它们共用一个底层数组。
函数间传递Map是不会拷贝一个该Map的副本的,也就是说如果一个Map传递给一个函数,该函数对这个Map做了修改,那么这个Map的所有引用,都会感知到这个修改。
func main() {
dict := map[string]int{"王五": 60, "张三": 43}
modify(dict)
fmt.Println(dict["张三"]) // 10
}
func modify(dict map[string]int) {
dict["张三"] = 10
}
上面这个例子输出的结果是10,也就是说已经被函数给修改了,可以证明传递的并不是一个Map的副本。这个特性和切片是类似的,这样就会更高,因为复制整个Map的代价太大了。
函数传参是值传递,所以对于结构体来说也不例外,结构体传递的是其本身以及里面的值的拷贝。
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的值可以通过传递结构体的指针,我们稍微改动下例子
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的值已经被改变。如果结构体里有引用类型的值,比如map,那么我们即使传递的是结构体的值副本,如果修改这个map的话,原结构的对应的map值也会被修改。
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转换问题
package main
import (
"encoding/json"
"fmt"
)
func main() {
// 由字符串unmarshal得到的结构
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 { // 数值型的要使用float64去尝试转换
fmt.Println(v) // -1
}
// 自然生成的结构
var cd2 cardMeta
var e, f int
e = 3
f = 4
cd2.LpUrl = e
cd2.OpenUrl = 6.5 // 默认为float64类型
// d := cd2.LpUrl + f 不可以,需要先把cd2.LpUrl转换
if v, ok := cd2.LpUrl.(int); ok {
fmt.Println(v + f) // 7
}
if v, ok := cd2.OpenUrl.(float64); ok { // 使用float32转换不行
fmt.Println(v) // 6.5
}
var ext float32
ext = 6.5
cd2.Ext = ext
if v, ok := cd2.Ext.(float32); ok { // 使用float64转换不行
fmt.Println(v) // 6.5
}
cd2.Ext2 = 3 // 默认为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会报错,因为原始内容数值加了双引号的
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"`
}