【Golang】深度解析json解码切片数据到未初始化的切片

前言

空指针异常在程序员的世界,真他妈是炸开锅了,今天为大家带来一个空指针的也能合理化写入数据的Golang经典小case

案例

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
)

func main() {
	var t = []int{1, 2, 3}
	marshal, err := json.Marshal(t)
	if err != nil {
		return
	}
	var v []int
	err = json.NewDecoder(bytes.NewReader(marshal)).Decode(&v)
	if err != nil {
		return
	}
	fmt.Println(v)
}

这段代码很常见,能够正常打印结果毫无疑问,但是!!!v这TM不是个空指针吗,json是怎么把数据搞进去,并且没有pannic,我一度好奇,今天晚上找了点时间专门研究了一下。。。直到我发现这段代码[/usr/local/go/src/encoding/json/decode.go]

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)

功夫再高,还是用的菜刀,这里明显给这个传入的变量分配了空间,让她的指针不再空虚···
内存咱算是找到了,数据呢?数据咋进去的

// decodeState represents the state while decoding a JSON value.
type decodeState struct {
	data                  []byte
	off                   int // next read offset in data
	opcode                int // last read result
	scan                  scanner
	errorContext          *errorContext
	savedError            error
	useNumber             bool
	disallowUnknownFields bool
}

毫无疑问我们传入的流式数据肯定是保存在这个数据结构中,肯定保存在data切片中!
是怎么怼进分配的内存去的呢
咱接着往下看。。。

if i < v.Len() {
	// Decode into element.
	if err := d.value(v.Index(i)); err != nil {
		return err
	}
} 

就是这里,把数据给装进去了,具体怎么装的?咱也瞧瞧

	case Slice: // 前面通过反射拿到的类型,这个就不用在这里赘述
		// Element flag same as Elem of Ptr.
		// Addressable, indirect, possibly read-only.
		s := (*unsafeheader.Slice)(v.ptr)
		if uint(i) >= uint(s.Len) {
			panic("reflect: slice index out of range")
		}
		tt := (*sliceType)(unsafe.Pointer(v.typ))
		typ := tt.elem
		val := arrayAt(s.Data, i, typ.size, "i < s.Len")
		fl := flagAddr | flagIndir | v.flag.ro() | flag(typ.Kind())
		return Value{typ, val, fl} //构建反射变量值

根据反射类型,创建一个反射变量

func (d *decodeState) value(v reflect.Value) error {
	switch d.opcode {
	...
	case scanBeginLiteral:
		// All bytes inside literal return scanContinue op code.
		start := d.readIndex()
		d.rescanLiteral()
		// 这里的v使我们之前构建的反射变量,而且是切片,都是根据我们传入的指针拿到的类型进行创建的
		if v.IsValid() {
			//z注意!!!开始写数据了
			if err := d.literalStore(d.data[start:d.readIndex()], v, false); err != nil {
				return err
			}
		}
		
	}
}

然后开始判断流式数据中的数据类型

switch c := item[0]
...
default: // number
...
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
	n, err := strconv.ParseInt(s, 10, 64)
		if err != nil || v.OverflowInt(n) {
		// 解析成int
			d.saveError(&UnmarshalTypeError{Value: "number " + s, Type: v.Type(), Offset: int64(d.readIndex())})
			break
		}
		// 这里就是真正的向反射变量里面写值
		v.SetInt(n)

...
func (v Value) SetInt(x int64) {
	v.mustBeAssignable()
	switch k := v.kind(); k {
	default:
		panic(&ValueError{"reflect.Value.SetInt", v.kind()})
	case Int:
		*(*int)(v.ptr) = int(x)

到这里,就从流式数据拿到第一个值,并且写入v中,然后循环写入就可以了,整个过程基本就是这个样子

总结一下

  1. 不要大惊小怪,一切魔法背后都是基本菜刀,重要的是刀法
  2. 主要知识点: 反射,unsafe指针【这玩意儿找时间好好理解一下】
  3. slice是内置类型,支持不奇怪
  4. 可以尝试把[]int替换成自定义类型,看看结果,感兴趣的小伙伴可以根据源码做个判断

思考一下

type Info struct {
	name string // 1
}
type Info struct {
	Name string  // 2
}

这两个解析出来有什么区别,为什么?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值