前言:
现在兄弟们对“算法输入输出 json”都比较关切,咱们都需要剖析一些“算法输入输出 json”的相关文章。那么小编同时在网络上搜集了一些有关“算法输入输出 json””的相关资讯,希望看官们能喜欢,看官们一起来了解一下吧!golang json序列化/反序列化的轮子一大票,很多人都吐槽官方库耗性能,但是性能耗在哪里?既然那么多优秀的开源库存在,golang官方包为啥不更新?带着这个疑问,基于go1.19分析下它的源码。
首先看下Marshal()函数,它的源码位于:encoding/json/encode.go,在看源码之前,我们先看下注释,这是最详尽的文档。
1,它允许每一种类型自定义序列化和反序列化方法,支持两种类型json和text,优先级如下:
A,如果字段不是nil,并且定义MarshalJSON方法就调用它的方法
B,如果没有定义上述这个方法,定义了encoding.TextMarshaler 就会调用,没有nil限制
C,否则按照官方的序列化方案按照下列规则序列化。
2,Marshal()函数会使用以下的基于类型的默认编码格式:
A,布尔类型编码为json布尔类型;
B,浮点数、整数和json.Number类型编码为json数字类型;
C,字符串类型编码为json字符串;UTF-8编码
D,数组和切片类型编码为json数组,但[]byte编码为base64编码字符串,nil切片编码为null;
E,结构体类型编码为json对象,每一个可导出字段(首字母大写)会变成该对象的一个成员。
F,不合法的字节会被编码成unicode。字符会被HTMLEscape 进行转码,"<", ">", "&", U+2028, and U+2029 会被替换成 "\u003c","\u003e", "\u0026", "\u2028", and "\u2029"。所以json在Html <script>标签内是安全的,如果不想被转码,使用SetEscapeHTML(false).
// replacing invalid bytes with the Unicode replacement rune.// So that the JSON will be safe to embed inside HTML <script> tags,// the string is encoded using HTMLEscape,// which replaces "<", ">", "&", U+2028, and U+2029 are escaped// to "\u003c","\u003e", "\u0026", "\u2028", and "\u2029".// This replacement can be disabled when using an Encoder,// by calling SetEscapeHTML(false).
G,json的key 默认是字段名,但是如果结构体设置了tag,key受到tag的影响,tag可以是逗号分割的多个字段。比如"omitempty",跳过空值。tag是"-"的字段会被跳过。如果json的key本身是 "-"可以在后面加一个逗号。
// Field int `json:"-,"`
H,tag的string选项会把值序列化成json的字符串
StringInt int64 `json:"StringInt,string"` StringString string `json:"StringString,string"`
序列化的值是
"StringInt":"0","StringString":"\"\"",
但是它适用的类型仅仅有 string, floating point,integer, or boolean types.
I,key的类型仅仅是Unicode letters, digits, and ASCII punctuation ,并且key不能是单双引号,反斜线,逗号。并且会对key做一些类型转换。
// subject to the UTF-8 coercion described for string values above:// - keys of any string type are used directly// - encoding.TextMarshalers are marshaled// - integer keys are converted to strings
J,Channel, complex, and function values 不会被序列化,会返回UnsupportedTypeError 错误
K,带环的数据结构,序列化会返回错误
L,当嵌套字段的字段和同级字段名字冲突的时候,如果加tag后名字不一样,都序列化,否则选择外面的,忽略嵌套的,不报错
下面,我们详细看下源码:
func Marshal(v any) ([]byte, error) { e := newEncodeState() err := e.marshal(v, encOpts{escapeHTML: true}) if err != nil { return nil, err } buf := append([]byte(nil), e.Bytes()...) encodeStatePool.Put(e) return buf, nil}
首先初始化了编码状态机,然后进行序列化,默认是进行html转义的,将序列化结果append到输出buf上,最后用sync.pool缓存编码状态机,方便下次复用,减少小对象的申请释放提升序列化速度。初始化序列化状态机的代码如下,它先从sync.pool里取,如果取不到,新定义一个
var encodeStatePool sync.Pool
func newEncodeState() *encodeState { if v := encodeStatePool.Get(); v != nil { e := v.(*encodeState) e.Reset() if len(e.ptrSeen) > 0 { panic("ptrEncoder.encode should have emptied ptrSeen via defers") } e.ptrLevel = 0 return e } return &encodeState{ptrSeen: make(map[any]struct{})}}
定义的过程就是初始化了一个map,用来存在序列化的过程中遇到的指针,防止出现环,从而导致栈溢出,详细可以看序列化状态机的注释
type encodeState struct { bytes.Buffer // accumulated output scratch [64]byte // Keep track of what pointers we've seen in the current recursive call // path, to avoid cycles that could lead to a stack overflow. Only do // the relatively expensive map operations if ptrLevel is larger than // startDetectingCyclesAfter, so that we skip the work if we're within a // reasonable amount of nested pointers deep. ptrLevel uint ptrSeen map[any]struct{}}
它内嵌了匿名field:bytes.Buffer ,每次从pool里取出来后会调用buf的Reset方法,重置buf。
下面就是核心的序列化方法,如果遇到panic,并且error类型是jsonError,就会被捕获,否则,继续panic。
func (e *encodeState) marshal(v any, opts encOpts) (err error) { defer func() { if r := recover(); r != nil { if je, ok := r.(jsonError); ok { err = je.error } else { panic(r) } } }() e.reflectValue(reflect.ValueOf(v), opts) return nil}
然后通过反射获取类型信息,根据输入的选项来进行序列化。
func (e *encodeState) reflectValue(v reflect.Value, opts encOpts) { valueEncoder(v)(e, v, opts)}
函数内部会根据类型,选用不同的序列化方法,其中序列化方法有三个参数,序列化状态机,反射的Value和选项
type encoderFunc func(e *encodeState, v reflect.Value, opts encOpts)
func valueEncoder(v reflect.Value) encoderFunc { return typeEncoder(v.Type())
在根据类型返回不同的序列化方法的过程中,也会充分利用sync.Map来缓存遇到过的类型和对应的序列化方法,避免重复的反射操作,来提升性能。
var encoderCache sync.Map // map[reflect.Type]encoderFunc
获取序列化方法的时候,优先从缓存获取,如果取不到,先找到序列化方法,然后缓存下来:
func typeEncoder(t reflect.Type) encoderFunc { if fi, ok := encoderCache.Load(t); ok { return fi.(encoderFunc) } fi, loaded := encoderCache.LoadOrStore(t, encoderFunc(func(e *encodeState, v reflect.Value, opts encOpts) { wg.Wait() f(e, v, opts) })) f = newTypeEncoder(t, true) wg.Done() encoderCache.Store(t, f)
由于json的类型是可以递归的,所以寻找序列化的过程也是递归进行的,外层缓存序列化方法到sync.Map的过程,通过waitGroup等待内层计算完毕后才缓存。内层计算本层的处理函数,计算完毕后告知外层,等待递归的请求。然后我们就进入到序列化的核心函数:
func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc { if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(marshalerType) { return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false)) if t.Implements(marshalerType) { return marshalerEncoder } if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(textMarshalerType) { return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false)) } if t.Implements(textMarshalerType) { return textMarshalerEncoder } switch t.Kind() { case reflect.Bool: return boolEncoder case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return intEncoder case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return uintEncoder case reflect.Float32: return float32Encoder case reflect.Float64: return float64Encoder case reflect.String: return stringEncoder case reflect.Interface: return interfaceEncoder case reflect.Struct: return newStructEncoder(t) case reflect.Map: return newMapEncoder(t) case reflect.Slice: return newSliceEncoder(t) case reflect.Array: return newArrayEncoder(t) case reflect.Pointer: return newPtrEncoder(t) default: return unsupportedTypeEncoder }
如果不是指针类型的话,检查它是否实现了类型接收器的序列化方法,其中marshalerType
marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem()
然后检查是否实现了指针接受器的序列化方法,接着检查非指针类型接收器和指针类型接收器的文本序列化方法
textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
如果类型实现了上述序列化方法,就会按照类型自己定义的序列化方法来序列化,否则就用系统默认的序列化方法来序列化。针对go的每一种内置类型,都定义了对应的序列化方法。比如uint
func uintEncoder(e *encodeState, v reflect.Value, opts encOpts) { b := strconv.AppendUint(e.scratch[:0], v.Uint(), 10) if opts.quoted { e.WriteByte('"') } e.Write(b) if opts.quoted { e.WriteByte('"') }}
float32和float64的序列化方法是一张的,只是初始化值不一样
type floatEncoder int // number of bitsfunc (bits floatEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
var ( float32Encoder = (floatEncoder(32)).encode float64Encoder = (floatEncoder(64)).encode)
然后就是按照编码规则,将浮点数编码成字符串:
func (bits floatEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { f := v.Float() if math.IsInf(f, 0) || math.IsNaN(f) { e.error(&UnsupportedValueError{v, strconv.FormatFloat(f, 'g', -1, int(bits))}) } if abs != 0 { if bits == 64 && (abs < 1e-6 || abs >= 1e21) || bits == 32 && (float32(abs) < 1e-6 || float32(abs) >= 1e21) { fmt = 'e' } } b = strconv.AppendFloat(b, f, fmt, -1, int(bits))
对于结构体类型来说,它是复合类型,会被序列化成json的map,它的类型和对应的序列化方法会被缓存到sync.Map,当然用的时候也优先尝试从map里获取。
func newStructEncoder(t reflect.Type) encoderFunc { se := structEncoder{fields: cachedTypeFields(t)} return se.encode}
func cachedTypeFields(t reflect.Type) structFields { if f, ok := fieldCache.Load(t); ok { return f.(structFields) } f, _ := fieldCache.LoadOrStore(t, typeFields(t)) return f.(structFields)}
它的序列化方法本身就是按照json协议拼字符串,对于每一个field,会递归调用序列化方法来序列化。
func (se structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { next := byte('{')FieldLoop: for i := range se.fields.list { f := &se.fields.list[i] if opts.escapeHTML { e.WriteString(f.nameEscHTML) } else { e.WriteString(f.nameNonEsc) } opts.quoted = f.quoted f.encoder(e, fv, opts)
调用自定义序列化方法的过程如下,空指针,或者没实现序列化方法,会序列化成“null”,否则调用MarshalJSON方法
func marshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) { if v.Kind() == reflect.Pointer && v.IsNil() { e.WriteString("null") return } m, ok := v.Interface().(Marshaler) if !ok { e.WriteString("null") return } b, err := m.MarshalJSON() if err == nil { // copy JSON into buffer, checking validity. err = compact(&e.Buffer, b, opts.escapeHTML) }
最后会进行转义
func compact(dst *bytes.Buffer, src []byte, escape bool) error { for i, c := range src { if escape && (c == '<' || c == '>' || c == '&') { if start < i { dst.Write(src[start:i]) } dst.WriteString(`\u00`) dst.WriteByte(hex[c>>4]) dst.WriteByte(hex[c&0xF]) start = i + 1 }
自定义序列化需要实现接口:
type Marshaler interface { MarshalJSON() ([]byte, error)}
至此,官方json序列化方法,介绍完毕,我们可以看到,虽然尽量使用了缓存的方法,来提升复用和减少反射操作,但是,对象和序列化方法的对应关系,还是在运行时通过反射的方式建立来写入sync.Map的,这里有两个比较低效的操作,map的写入和反射建立类型与序列化方法对应关系。在明确知道类型的情况下,这个过程其实可以在编译时完成,减少运行时的消耗。在同一类型反复序列化的场景,官方的库通过缓存的方式,能够提升后面几次序列化的性能。
标签: #算法输入输出 json