ken

2019-05-29  本文已影响0人  一斗

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"`
}

上一篇下一篇

猜你喜欢

热点阅读