写golang restful接口时遇到的一个坑

来源:互联网 发布:淘宝游戏王正版 编辑:程序博客网 时间:2024/06/06 07:17

话不多说,先上代码

type detail struct {    High float64 `json:"high"`     Low float64 `json:"low"`    Average float64 `json:"average"`}type Spot struct {    UpdateDate string `json:"update_date"`    Detail []detail `json:"detail"`}    .    .//此处省略部分无关代码    .    key := constant.REDIS_INFIX_PREMIUM + productId  //此处是去redis读数据    d, err := c.ZRevRangeByScore(key, maxScores, minScores, page, num)//取出的[][]byte放入d中    if err != nil {     logger.Warnning(err)     return nil, err    }    var (     prices []Spot     p      Spot    )    for _, v := range d {        //遍历[][]byte     err = json.Unmarshal(v, &p) //反序列化赋给p     if err != nil {        logger.Warnning(err)        return nil, err     }     prices = append(prices, p) //每个价格点都加入到prices切片中,然后返回    }    return prices, nil


       乍一看这段代码没什么问题,但是实际上调试的时候,发现prices切片中的每个元素,也就是每个Spot对象的Detail字段都是一模一样的(UpdateDate字段没问题),一开始怀疑是存redis时出了问题,后来发现redis里的内容是对的。

让我来加入一些调试代码,把地址什么的都打印出来:

for _, v := range d {        //遍历[][]byte     err = json.Unmarshal(v, &p) //反序列化赋给p     fmt.Printf("p的Detail的Data的地址: %p \n",p.Sli)//每次循环打印p的Detail的Data的地址     if err != nil {        logger.Warnning(err)        return nil, err     }     prices = append(prices, p) //每个价格点都加入到prices切片中,然后返回    }    fmt.Println(prices) //打印每个p的内容    .    .    .    打印结果:    p的Detail的Data的地址: 0xc0420420c0     p的Detail的Data的地址: 0xc0420420c0  //指向的地址未发生改变    [{2017-11-08 [{250 750 500} {250 750 500}]} {2017-11-09 [{250 750 500} {250 750 500}]}]  //日期后面就是Detail字段,发现都变得一模一样



       后来排查了很久,其实是因为p内部包含了一个切片,也就是Detail,然后这个切片会指向一个地址,真正的Data是存在这个地址中的,如果切片容量够的情况下,是不会改变指向的地址的。这就导致了,最后一个Spot对象的Detail切片的Data覆盖了之前append进去的所有Spot对象的Detail的Data,因为它们指向的都是一个地址,要改变当然一起改变了。看上面的地址。

       其实修改起来很简单,只要把var p Spot加到循环中,而不是放在循环外面就可以了。道理很简单,每次新建变量的时候,Detail会指向一个新的地址。

for _, v := range d {             var p Spot  //变量申明放入循环内     err = json.Unmarshal(v, &p)      fmt.Printf("p的Detail的Data的地址: %p \n",p.Sli)//每次循环打印p的Detail的Data的地址     if err != nil {        logger.Warnning(err)        return nil, err     }     prices = append(prices, p)     }    fmt.Println(prices) //打印每个p的内容    .    .    .    打印结果:    p的Detail的Data的地址: 0xc0420420c0     p的Detail的Data的地址: 0xc042042120  //地址改变了    [{2017-11-08 [{500 600 550} {500 600 550}]} {2017-11-09 [{250 750 500} {250 750 500}]}]   //结果正确了


后来又发现,其实还有个方法可以避免这个问题,就是在申明结构体的时候,不要把Detail定义为Slice,定义为一个空的interface就行,但是传的时候还是传切片,在Unmarshal的时候,golang发现这是个空接口,就会去给它申请新的地址,后来验证是没问题的,即:

type Spot struct {    UpdateDate string `json:"update_date"`    Detail interface{} `json:"detail"`}

       之前自以为对slice很熟悉,也知道它的内部结构是会指向一个地址的,但是真正用起来还是要多加注意,一不小心就忽略了,引以为戒啊。

原创粉丝点击