打赏

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

posted @ 2022-11-27 21:55  苍山落暮  阅读(1015)  评论(0编辑  收藏  举报