Golang中encoding/binary包的应用
介绍
golang的binary包简单实现了数字(number)到字节序(byte sequences)的转换,以及64位整型(varint)的编码与解码。
首先简单看一下binary的结构与方法
理解字节序(Byte sequences)、大字端(Big Endian)、小字端(Little Endian)
字节序,即字节在电脑中存放时的序列与输入(输出)时的序列是先到的在前还是后到的在前。包含大字端与小字节端
一个内存地址能存储1 byte的数据
大字端:按照低地址位到高地址位的顺序存放高字节到低字节,既高字节在前,低字节在后。
小字端:与大字端相反,按照低地址位到高地址位的顺序存放低字节到高字节,既低字节在前,高字节在后。
看这个文字描述比较晦涩难懂,下面举个例子
eg.
那一个64位10进制int数287454020举例子,正好对应16进制的0x11223344,那么用一个byte数组存储的方式如下
内存地址 | 0x00000001 | 0x00000002 | 0x00000003 | 0x00000004 |
---|---|---|---|---|
Big Endian | 11 | 22 | 33 | 44 |
Little Endian | 44 | 33 | 22 | 11 |
绝大部分CPU都是按照Little Endian方式存储数据的,但是用于网络传输的时候大部门是使用Big Endian方式作为数据传输的。
//定义一个长整型16进制数0x11223344,也就是10进制的287454020
num := int64(0x1122334455667788)
//声明两个Writer的实现buffer,长度8,因为长整型int是64位,也就是8字节
bigEndianBuffer := bytes.NewBuffer(make([]byte, 0))
littleEndianBuffer := bytes.NewBuffer(make([]byte, 0))
//将变量写入流中 一个使用高字端,一个使用低字端
_ = binary.Write(bigEndianBuffer, binary.BigEndian, num)
_ = binary.Write(littleEndianBuffer, binary.LittleEndian, num)
fmt.Println(bigEndianBuffer.Bytes())
//[17 34 51 68 85 102 119 136] 其实对应的16进制就是
//[0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88]
fmt.Println(littleEndianBuffer.Bytes())
//[136 119 102 85 68 51 34 17] 与上面结果正好相反,对应的16进制就是
//[0x88 0x77 0x66 0x55 0x44 0x33 0x22 0x11]
可变长度编码(Variable-length encoding)
可变长度整数(以下简称为varint)压缩算法是将整数压缩成比通常需要的更小空间的一种方法。一个varint算法以用一个字节表示10,而用4个字节来表示8亿。
比如,在应用中,大多数的值都在0到100之间,而有些值可能会超过16384,如果使用固定长度的空间来表示这些值的话,就需要一个完整的32位整数,即使大多数值用单个字节来表示就够了。
正是因为在中大多数数字的分布并不均匀,varint算法才有了用武之地。通常情况下,较小的数字出现的概率大于较大的数字。varint算法作出的权衡是,用较小的空间存储小数字,而用较大的空间存储大数字。因此,采用这种算法来对整数进行编码是有意义的,它可以节省存储数据需要的空间或者传输数据时所需的带宽。
两种varint编码的常见方式是使用前缀长度和使用连续位标识。
连续位标识
varint算法与Protobuf的一致,Protobuf用的是连续位标识技术,使用每个字节的第一位来标识是否需要继续向后读。每个字节低7位用于实际的编码。
比如对于数字25,8位二进制为0001 1001。注意最左边一位是0,在Protobuf中,这意味着不需要继续向后读了。采用这样的技术,0到127之间的数字都可以用一个字节表示。
对于大于127的数,比如225,二进制为1110 0001,如果用7个bit进行编码,则得到两个分组000 0001和110 0001。对Protobuf来说,最不重要的分组首先出现,这意味着应该向低阶组添加一个连续位0 000 0001和1 110 0001。逆置分组后,得到1110 0001和0000 0001。这样就使用两个字节对225进行了编码。
解码的过程如下,先读一个字节,如果该字节的高位第一个bit为1,则继续读;如果为0,则停止。移除每个字节的第一个bit,逆置剩余的bit分组,重新组合后得到原始的数据。
还是以225为例。
- 读取到的字节为11100001和00000001。
- 移除首bit后得到11000001和0000001。
- 逆置后得到0000001和1100001。
- 得到11100001,即为225。
这个技术非常强大,它可以编码任意大小的数字。
binary包下的函数 putVarint() 和 putUvarint() 把可变长值写到内存字节切片中。
这两个函数把 x 编码到 buf 中并返回写入 buf 中字节的长度,如果 buf 初始化长度过小(比 x 还要小)函数就会 panic , 建议使用 binary.MaxVarintLen64 常量确保出现 panic 的情况。
变量
binary.BigEndian:对应以大字端方式
binary.LittleEndian:对应以小字端方式
binary.MaxVarintLen16、binary.MaxVarintLen32、binary.MaxVarintLen64: 可变长度编码建议初始化的byte切面长度
用于binary的相关方法作为存储方式的参数。
方法
Read:按照指定的字节序读取数据流(io.Reader)到一个指针变量。
Write:按照指定的字节序将指针变量写入数据流(io.Wrter)中。
PutUvarint:解码无符号的Varint(64位uint)
PutVarint:解码Varint(64位int)
Uvarint:编码无符号的Varint(64位uint)
Varint:编码Varint(64位int)
https://www.jianshu.com/p/a52c16fca39e
https://blog.csdn.net/qq_31967569/article/details/82689039