寫golang restful介面時遇到的一個坑
阿新 • • 發佈:2019-02-07
話不多說,先上程式碼
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很熟悉,也知道它的內部結構是會指向一個地址的,但是真正用起來還是要多加註意,一不小心就忽略了,引以為戒啊。