龙空技术网

golang源码分析:encoding/json(1)

go算法架构leetcode 107

前言:

现在兄弟们对“算法输入输出 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