Go语言之encoding/binary源码分析

404 阅读3分钟

「这是我参与2022首次更文挑战的第30天,活动详情查看:2022首次更文挑战

encoding/binary

这个包有两个功能:

  • 数值和字节序之间的转换
  • 可变长的编解码

这个包追求的是简单,而不是高性能,如果场景需要高性能, 尤其是针对大数据结构体,应该考虑encoding/gop或者pb.

源码分析

这个包包含两个源文件 binary.go和varint.go,分别承载binary包的两个功能. 那一一来分析.

reflect包中的常用函数

  • reflect.VauleOf()
    • 用参数接口对象的值去初始化一个reflect.Value
  • reflect.Indirect()
    • 获取参数reflect.Value实际指向的值
    • 如果参数是指针,就返回指针指向的值;如果不是,就返回参数本身
  • reflect.Value.Type()
    • 获取Value的类型reflect.Type
    • relfect.Type是一个接口
  • reflect.Type.Kind()
    • 返回reflect.Type对应的reflect.Kind
    • reflect.Kind是一个常量枚举,这个枚举里包含了Go中定义的各种类型
    • reflect.Kind.String()会返回对应类型的字符串
  • reflect.Type.Elem()
    • 类型Type的元素类型
    • 下列类型中才会包含元素类型
      • 数组/切片/指针/信道/map

binary.go

先确定分析顺序.需要看对外暴露的:

  • 接口类型ByteOrder,两个实现类型 BigEndian和LittleEndian,分别是大小端
  • 3个函数 Read/Write/Size, 类型ByteOrder就作为读写函数的参数

这个文件最重要就是Read/Write函数,这两个函数的参数又使用了ByteOrder, 所以需要先分析大小端字节序,同时Read/Write的实现中还用到了decoder/encoder, 这两个类型都包含了ByteOrder字段.所以分析顺序如下:

  • ByteOrder接口类型
  • BigEndian/LittleEndian类型
  • coder/decoder/encoder
  • Read/Write
  • Size

ByteOrder接口类型分析:

// A ByteOrder specifies how to convert byte sequences into
// 16-, 32-, or 64-bit unsigned integers.
type ByteOrder interface {
  Uint16([]byte) uint16
  Uint32([]byte) uint32
  Uint64([]byte) uint64
  PutUint16([]byte, uint16)
  PutUint32([]byte, uint32)
  PutUint64([]byte, uint64)
  String() string
}

这个ByteOrder字节序类型只定义了[]byte如何转换为无符号数值. 看接口定义,ByteOrder也定义了无符号数值如何转换成[]byte.

我们看下网上对大小端的定义:

  • 大端:高字节保存在低地址,低字节保存在高地址
  • 小端:高字节保存在高地址,低字节保存在低地址

下面是一个例子:

var a uint32 = 0x12345678;
var buf [4]byte;
// 大端, 符合阅读习惯
// buf[0] = 0x12
// buf[1] = 0x34
// buf[2] = 0x56
// buf[3] = 0x78
// 小端, 符合逻辑
// buf[0] = 0x78
// buf[1] = 0x56
// buf[2] = 0x34
// buf[3] = 0x12

ByteOrder对16/32/64位的数值和[]byte做了一个转换.

    // 空结构体,表示只关注方法,不在乎数据
    type littleEndian struct{}

    // 小端模式将`[]byte`转换为uint16
    // 边界检查丢给了编译器
    // 测试发现,其实并不是在编译期将错误抛出来
    // 而是写了一个边界检查,还是在运行时报错
    // issue14808提出了一个解决方法,用以兼容不满足边界检查的场景
    // 小端,低位放低地址,16表示只处理16位
    func (littleEndian) Uint16(b []byte) uint16 {
      _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
      return uint16(b[0]) | uint16(b[1])<<8
    }

    func (littleEndian) PutUint16(b []byte, v uint16) {
      _ = b[1] // early bounds check to guarantee safety of writes below
      b[0] = byte(v)
      b[1] = byte(v >> 8)
    }

    func (littleEndian) Uint32(b []byte) uint32 {
      _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808
      return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
    }

    func (littleEndian) PutUint32(b []byte, v uint32) {
      _ = b[3] // early bounds check to guarantee safety of writes below
      b[0] = byte(v)
      b[1] = byte(v >> 8)
      b[2] = byte(v >> 16)
      b[3] = byte(v >> 24)
    }

    func (littleEndian) Uint64(b []byte) uint64 {
      _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
      return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 |
      uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40 |
      uint64(b[6])<<48 | uint64(b[7])<<56
    }

    func (littleEndian) PutUint64(b []byte, v uint64) {
      _ = b[7] // early bounds check to guarantee safety of writes below
      b[0] = byte(v)
      b[1] = byte(v >> 8)
      b[2] = byte(v >> 16)
      b[3] = byte(v >> 24)
      b[4] = byte(v >> 32)
      b[5] = byte(v >> 40)
      b[6] = byte(v >> 48)
      b[7] = byte(v >> 56)
    }

    // 仅仅是输出和字节序相关的信息
    func (littleEndian) String() string { return "LittleEndian" }
    func (littleEndian) GoString() string { return "binary.LittleEndian" }

至此小端已经分析完了,大端只是对应不同而已,就不贴代码了.