前言
Go語言愛好者周刊:第 65 期的刊首語貼出這麼一道題:
package main import ( "encoding/json" "fmt" ) type AutoGenerated struct { Age int `json:"age"` Name string `json:"name"` Child []int `json:"child"` } func main() { jsonStr1 := `{"age": 14,"name": "potter", "child":[1,2,3]}` a := AutoGenerated{} json.Unmarshal([]byte(jsonStr1), &a) aa := a.Child fmt.Println(aa) jsonStr2 := `{"age": 12,"name": "potter", "child":[3,4,5,7,8,9]}` json.Unmarshal([]byte(jsonStr2), &a) fmt.Println(aa) }
他看到了網上的解釋,想不通。我看了下,他看到的解釋是不對的。
你覺得輸出是什麼呢?
A:[1 2 3] [1 2 3] ;B:[1 2 3] [3 4 5]; C:[1 2 3] [3 4 5 6 7 8 9];D:[1 2 3] [3 4 5 0 0 0]
問題解答
題目主要考究的就是slice的特性。
slice的三要素,指針(指向底層數組)、長度和容量。
代碼中可以看出,slice aa變量除列印外,并沒有參與其他的處理,根據slice的三要素,我們可知aa的長度和容量不會發生變化,是以可以排除C、D選項。
其次,aa持有的是a.Child的底層數組,其長度與cap對應a.Child的長度及cap。第一次json.Unmarshal,a.Child的結果應為
[]int{1,2,3}
,長度為3,則
aa=a.Child[:3]
。再次Unmarshal後a.Child對應的slice為
[]int{3,4,5,7,8,9}
,則aa對應的就是
a.Child[:3]
,則aa的結果為[]int{3,4,5}。
問題延伸
前後兩次解析後,a.Child的len和cap如何變化的?
func main() {
jsonStr1 := `{"age": 14,"name": "potter", "child":[1,2,3]}`
a := AutoGenerated{}
json.Unmarshal([]byte(jsonStr1), &a)
fmt.Println(len(a.Child),cap(a.Child))
jsonStr2 := `{"age": 12,"name": "potter", "child":[3,4,5,7,8,9]}`
json.Unmarshal([]byte(jsonStr2), &a)
fmt.Println(len(a.Child),cap(a.Child))
}
關于len的話,我們可以明确地指導,其長度與解析資料的長度一緻。那麼cap呢?
我們直接把代碼運作下,得到先後兩次的cap為4,6。為什麼會是這樣呢?
第一次運作後的a.Child是不足以容納第二次的資料的,在第二次解析時肯定需要擴容?我們了解的append的擴容規則,在小于1024時,通常是翻倍擴容,這裡的結果卻不是?說明其采用的并不是append的擴容。那麼json.Unmarshal中slice又是怎麼擴容的呢?
問題追蹤
解析json數組的核心代碼如下:
// array consumes an array from d.data[d.off-1:], decoding into v.
// The first byte of the array ('[') has been read already.
func (d *decodeState) array(v reflect.Value) error {
// Check for unmarshaler.
u, ut, pv := indirect(v, false)
if u != nil {
start := d.readIndex()
d.skip()
return u.UnmarshalJSON(d.data[start:d.off])
}
if ut != nil {
d.saveError(&UnmarshalTypeError{Value: "array", Type: v.Type(), Offset: int64(d.off)})
d.skip()
return nil
}
v = pv
// Check type of target.
switch v.Kind() {
case reflect.Interface:
if v.NumMethod() == 0 {
// Decoding into nil interface? Switch to non-reflect code.
ai := d.arrayInterface()
v.Set(reflect.ValueOf(ai))
return nil
}
// Otherwise it's invalid.
fallthrough
default:
d.saveError(&UnmarshalTypeError{Value: "array", Type: v.Type(), Offset: int64(d.off)})
d.skip()
return nil
case reflect.Array, reflect.Slice:
break
}
i := 0
for {
// Look ahead for ] - can only happen on first iteration.
d.scanWhile(scanSkipSpace)
if d.opcode == scanEndArray {
break
}
// Get element of array, growing if necessary.
if v.Kind() == reflect.Slice {
// Grow slice if necessary
if i >= v.Cap() {
newcap := v.Cap() + v.Cap()/2
if newcap < 4 {
newcap = 4
}
newv := reflect.MakeSlice(v.Type(), v.Len(), newcap)
reflect.Copy(newv, v)
v.Set(newv)
}
if i >= v.Len() {
v.SetLen(i + 1)
}
}
if i < v.Len() {
// Decode into element.
if err := d.value(v.Index(i)); err != nil {
return err
}
} else {
// Ran out of fixed array: skip.
if err := d.value(reflect.Value{}); err != nil {
return err
}
}
i++
// Next token must be , or ].
if d.opcode == scanSkipSpace {
d.scanWhile(scanSkipSpace)
}
if d.opcode == scanEndArray {
break
}
if d.opcode != scanArrayValue {
panic(phasePanicMsg)
}
}
if i < v.Len() {
if v.Kind() == reflect.Array {
// Array. Zero the rest.
z := reflect.Zero(v.Type().Elem())
for ; i < v.Len(); i++ {
v.Index(i).Set(z)
}
} else {
v.SetLen(i)
}
}
if i == 0 && v.Kind() == reflect.Slice {
v.Set(reflect.MakeSlice(v.Type(), 0, 0))
}
return nil
}
其中關于擴容的核心邏輯如下:
// Get element of array, growing if necessary.
if v.Kind() == reflect.Slice {
// Grow slice if necessary
if i >= v.Cap() {
newcap := v.Cap() + v.Cap()/2
if newcap < 4 {
newcap = 4
}
newv := reflect.MakeSlice(v.Type(), v.Len(), newcap)
reflect.Copy(newv, v)
v.Set(newv)
}
if i >= v.Len() {
v.SetLen(i + 1)
}
}
當slice需要擴容時,則增長一半,newcap變為
cap+cap/2
(這是第二次cap為6的原因);若
newcap<4
,則置
newcap=4
(這是第一次cap為4的原因)。
擴容的slice是主動調用reflect.MakeSlice生成的。
延伸一下,第三次解析代碼如下,則a.Child的cap将會是多少呢?
jsonStr3 := `{"age": 12,"name": "potter", "child":[7,8,9,0,1,2,3,4,5,6]}`
json.Unmarshal([]byte(jsonStr2), &a)
總結
本文主要從一個問題開始,詳細解析了關于slice及json.Unmarshal到slice的部分特性。
slice
- slice的三要素,指針(指向底層數組)、長度和容量。
- 未參與運算(擴容、進一步slice)的話,長度和容量是不會變化。
- 由于其持有的是底層數組的指針,是以數組資料發生變化,其資料也會發生變化
json unmarshal to slice
- 具體擴容規則如下:
newcap := v.Cap() + v.Cap()/2 if newcap < 4 { newcap = 4 }
- 使用建議:
在使用json Umarshal時,如果知道slice的長度,則提前初始化長度,則可以減少擴容的過程,提高資料解析的速度。
公衆号
鄙人剛剛開通了公衆号,專注于分享Go開發相關内容,望大家感興趣的支援一下,在此特别感謝。