前言
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开发相关内容,望大家感兴趣的支持一下,在此特别感谢。