话不多说,先上代码
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很熟悉,也知道它的内部结构是会指向一个地址的,但是真正用起来还是要多加注意,一不小心就忽略了,引以为戒啊。