空指针异常在程序员的世界,真他妈是炸开锅了,今天为大家带来一个空指针的也能合理化写入数据的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中,然后循环写入就可以了,整个过程基本就是这个样子
总结一下- 不要大惊小怪,一切魔法背后都是基本菜刀,重要的是刀法
- 主要知识点: 反射,unsafe指针【这玩意儿找时间好好理解一下】
- slice是内置类型,支持不奇怪
- 可以尝试把[]int替换成自定义类型,看看结果,感兴趣的小伙伴可以根据源码做个判断
type Info struct {
name string // 1
}
type Info struct {
Name string // 2
}
这两个解析出来有什么区别,为什么?