golang-binary入门
golang encoding/binary入门
字节序
字节序主要是指存储数值时在内存中对字节的存储顺序,即字节在电脑中存放时的序列与输入(输出)时的序列是先到的在前还是后到的在前.
大字端:按照低地址位到高地址位的顺序存放高字节到低字节,既高字节在前,低字节在后。
小字端:与大字端相反,按照低地址位到高地址位的顺序存放低字节到高字节,既低字节在前,高字节在后。
64位10进制int数287454020,正好对应16进制的0x11223344如下:
内存地址 0x00000001 0x00000002 0x00000003 0x00000004
Big Endian 11 22 33 44
Little Endian 44 33 22 11
说明:
绝大部分CPU都是按照Little Endian方式存储数据的,但是用于网络传输的时候大部门是使用Big Endian方式作为数据传输的。因为大端序最高有效字节排在首位(低地址端存放高位字节),能够按照字典排序,所以我们能够比较二进制编码后数字的每个字节。
可变长度编码 Variable-length encoding
固定长度编码对存储空间的占用不灵活,比如一个 int64 类型范围内的值,当值较小时就会产生比较多的 0 字节无效位,直至达到 64 位。使用可变长度编码可限制这种空间浪费。
可变长度整数(以下简称为varint)压缩算法是将整数压缩成比通常需要的更小空间的一种方法。一个varint算法以用一个字节表示10,而用4个字节来表示8亿。
可变长原理:
每个字节的首位存放一个标识位,用以表明是否还有更多字节要读取及剩下的七位是否真正存储数据。标识位分别为 0 和 1
1 表示还要继续读取该字节后面的字节
0 表示停止读取该字节后面的字节
一旦所有读取完所有的字节,每个字节串联的结果就是最后的值。
举例说明:数字 53 用二进制表示为 110101 ,需要六位存储,除了标识位还剩余七位,所以在标识位后补 0 凑够七位,最终结果为 00110101。标识位 0 表明所在字节后面没有字节可读了,标识位后面的 0110101 保存了值。
再来一个大点的数字举例,1732 二进制使用 11011000100 表示,实际上只需使用 11 位的空间存储,除了标识位每个字节只能保存 7 位,所以数字 1732 需要两个字节存储。第一个字节使用 1 表示所在字节后面还有字节,第二个字节使用 0 表示所在字节后面没有字节,最终结果为:10001101 01000100
固定长度编码 Fixed-length encoding
Go 中有多种类型的整型, int8, int16, int32 和 int64 ,分别使用 1, 3, 4, 8 个字节表示,我们称之为固定长度类型 (fixed-length types)。
golang bianry使用
1.读写二进制文件
1.1 写入二进制文件
// 创建bin文件
fp, err := os.Create("bin")
// 关闭文件
defer fp.Close()
// 字节数据
b := []byte("abcdefghijklmn")
// 创建一个buffer
buf := new(bytes.Buffer)
// 使用小端 将字节数据写入buffer
binary.Write(buf, binary.LittleEndian, b) // encoding/binary包
// 将buffer中的数据写入文件
fp.Write(buf.Bytes()) // 生成bin文件
1.2 读取二进制文件
// 打开bin文件
fp,err := os.Open("bin")
// 关闭文件
defer fp.Close()
// 创建一个byte切片
buff := make([]byte, 20) // 20为该文本长度
// 读取文件
for {
// 读取文件到buffer中
lens,err := fp.Read(buff)
// 读完后结束
if err == io.EOF || lens < 0 {
break
}
}
// 输出读取的内容
fmt.Print(string(buff))
2.读取和写入固定长度的字节
2.1 写入和读取固定长度的数字
// 写入uint32的数字
v := uint32(500)
// 创建一个4个字节长度的切片,uint32占用四个字节长度
buf := make([]byte,4)
// 大端写入
binary.BigEndian.PutUint32(buf,v)
// 小端与之类似
// binary.LittleEndian.PutUint32(buf,v)
// 读取,大端读取
x := binary.BigEndian.Uint32(buf)
// 小端的读取
// binary.LittleEndian.Uint32(buf)
2.2 固定长度流的写入和读取
// (1.) 写入pi到二进制流
// 定义pi变量,float64类型
var pi float64 = math.Pi
// 创建一个bytes buffer
buf := new(bytes.Buffer)
// 使用小端 写入pi的值
binary.Write(buf,binary.LittleEndian,pi)
// 查看写入的字节
fmt.Printf("% x", buf.Bytes()) // 18 2d 44 54 fb 21 09 40
//------------------------------------------------------------//
// (2.)读取
// 定义pi的二进制数据
piByte := []byte{0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40}
// 定义解码
boolByte := []byte{0x00} // 0字节为false
// 读取byte和bool数据
piBuffer := bytes.NewReader(piByte)
boolBuffer := bytes.NewReader(boolByte)
// 读取字节数据到二进制
var piVar float64
binary.Read(piBuffer,binary.LittleEndian,&piVar)
fmt.Println("pi",piVar) // pi 3.1414926
// var boolVar bool
binary.Read(boolBuffer,binary.LittleEndian.&boolVar)
fmt.Println("bool",boolVar) // bool false
3.可变长度的字节序
3.1 写入可变长值到字节切片中
func PutVarint(buf []byte, x int64) int
func PutUvarint(buf []byte, x uint64) int
示例:
buf := make([]byte, binary.MaxVarintLen64)
for _, x := range []int64{-65, 1, 2, 127, 128, 255, 256} {
n := binary.PutVarint(buf, x)
fmt.Printf(x, "输出的可变长度为:%v,十六进制为:%x", n, buf[:n]")
}
3.2 字节码转十进制
func Varint(buf []byte) (int64, int)
func Uvarint(buf []byte) (uint64, int)
示例:
inputs := [][]byte{
[]byte{0x81, 0x01},
[]byte{0x7f},
[]byte{0x03},
[]byte{0x01},
[]byte{0x00},
[]byte{0x02},
[]byte{0x04},
[]byte{0x7e},
[]byte{0x80, 0x01},
}
for _, b := range inputs {
x, n := binary.Varint(b)
if n != len(b) {
fmt.Println("Varint did not consume all of in")
}
fmt.Println(x) // -65,-64,-2,-1,0,1,2,63,64,
}
3.3 可变长度字节流数据读取
func ReadVarint(r io.ByteReader) (int64, error)
func ReadUvarint(r io.ByteReader) (uint64, error)
示例:
var sbuf []byte
var buf []byte = []byte{144, 192, 192, 129, 132, 136, 140, 144, 16, 0, 1, 1}
var bbuf []byte = []byte{144, 192, 192, 129, 132, 136, 140, 144, 192, 192, 1, 1}
num, err := binary.ReadUvarint(bytes.NewBuffer(sbuf))
fmt.Println(num, err) //0 EOF
num, err = binary.ReadUvarint(bytes.NewBuffer(buf))
fmt.Println(num, err) //1161981756374523920 <nil>
num, err = binary.ReadUvarint(bytes.NewBuffer(bbuf))
fmt.Println(num, err) //4620746270195064848 binary: varint overflows a 64-bit integer
3.5 计算长度
Size讲返回数据系列化之后的字节长度,数据必须是固定长数据类型、slice和结构体及其指针
var a int
p := &a
b := [10]int64{1}
s := "adsa"
bs := make([]byte, 10)
fmt.Println(binary.Size(a)) // -1
fmt.Println(binary.Size(p)) // -1
fmt.Println(binary.Size(b)) // 80
fmt.Println(binary.Size(s)) // -1
fmt.Println(binary.Size(bs)) // 10
3.6 客户端和服务端通信
// 对数据进行编码
func Encode(id uint32, msg []byte) []byte {
var dataLen uint32 = uint32(len(msg))
// *Buffer实现了Writer
buffer := bytes.NewBuffer([]byte{})
// 将id写入字节切片
if err := binary.Write(buffer, binary.LittleEndian, &id); err != nil {
fmt.Println("Write to buffer error:", err)
}
// 将数据长度写入字节切片
if err := binary.Write(buffer, binary.LittleEndian, &dataLen); err != nil {
fmt.Println("Write to buffer error:", err)
}
// 最后将数据添加到后面
msg = append(buffer.Bytes(), msg...)
return msg
}
// 解码,从字节切片中获取id和len
func Decode(encoded []byte) (id uint32, l uint32) {
buffer := bytes.NewBuffer(encoded)
if err := binary.Read(buffer, binary.LittleEndian, &id); err != nil {
fmt.Println("Read from buffer error:", err)
}
if err := binary.Read(buffer, binary.LittleEndian, &l); err != nil {
fmt.Println("Read from buffer error:", err)
}
return id, l
}
详见:https://blog.csdn.net/Peerless__/article/details/121443159
参考链接
https://www.cnblogs.com/-wenli/p/12323809.html
https://pkg.go.dev/encoding/binary#example-Write
http://c.biancheng.net/view/4570.html
https://www.jianshu.com/p/ec461b39bf43