encoding 编解码
Go语言的encoding包中提供了很多常见的编解码、序列化和反序列化功能,比如二进制、Base64、JSON、XML等,这篇笔记我们简单介绍这些编码工具的用法。
encoding/base64 Base64编解码
encoding/base64包提供了base64的编解码工具,下面是一些例子代码。
package main
import (
"encoding/base64"
"fmt"
)
func main() {
str := "Hello, world!"
// BASE64编码
base64Str := base64.StdEncoding.EncodeToString([]byte(str))
// BASE64解码
bytes, err := base64.StdEncoding.DecodeString(base64Str)
if err != nil {
fmt.Println(err)
return
}
srcStr := string(bytes)
fmt.Println(base64Str)
fmt.Println(srcStr)
}
代码中,我们对字符串s调用了EncodeToString()方法将其编码为BASE64字符串,然后又用DecodeString()方法将其解码为[]byte数组,最后转换为原字符串。
encoding/json JSON序列化和反序列化
encoding/json包能够实现struct结构体或是map与JSON字符串之间相互转换。其中主要包含两个方法:
Marshal():序列化为JSON字符串
Unmarshal():反序列化JSON
struct序列化为JSON
下面代码是将结构体序列化为JSON的例子。
package main
import (
"encoding/json"
"fmt"
)
type RespJson struct {
Status int `json:"status"`
Msg string `json:"msg"`
}
func main() {
resp := RespJson{200, "success"}
bytes, err := json.Marshal(&resp)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(bytes))
}
上面代码中在定义结构体时,类型后面还有一个反引号包裹的字符串,这被称为tag语法,我们通过Tag标签的形式指定了序列化为JSON时该字段的字段名。
JSON反序列化为struct
下面代码中,我们将一个JSON字符串反序列化为了一个结构体对象。
package main
import (
"encoding/json"
"fmt"
)
type RespJson struct {
Status int `json:"status"`
Msg string `json:"msg"`
}
func main() {
str := "{\"status\":200,\"msg\":\"success\"}"
resp := RespJson{}
if err := json.Unmarshal([]byte(str), &resp); err != nil {
fmt.Println(err)
return
}
fmt.Println(resp)
}
map序列化为JSON
除了结构体,Marshal()和Unmarshal()方法也支持map,下面例子我们将一个map序列化为了JSON对象。
package main
import (
"encoding/json"
"fmt"
)
func main() {
m := make(map[string]interface{})
m["status"] = 200
m["msg"] = "success"
bytes, err := json.Marshal(m)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(bytes))
}
和其它语言一样,我们应该尽量避免使用map作为JSON数据对应的存储形式,大量的map会降低代码的可读性,不仅更容易出错而且会导致代码后续难以维护和修改。
encoding/binary 二进制读写
encoding/binary包提供了将基本数据类型与字节序列之间进行转换的功能。这个包的主要用途是对数据进行二进制编码和解码操作,特别适合在网络通信、二进制文件读写等需要处理原始二进制数据的场景中使用。
下面例子我们使用encoding/binary读写uint32和一个字符串的二进制数组。
package main
import (
"bytes"
"encoding/binary"
"fmt"
)
func main() {
// 创建动态缓冲区
var buf = bytes.Buffer{}
// 要写入的数据
var value uint32 = 13
str := "Hello, world!"
// 写入数据
if err := binary.Write(&buf, binary.BigEndian, value); err != nil {
fmt.Println(err)
return
}
if err := binary.Write(&buf, binary.BigEndian, []byte(str)); err != nil {
fmt.Println(err)
return
}
// 读取数据
strBytes := make([]byte, 13)
if err := binary.Read(&buf, binary.BigEndian, &value); err != nil {
fmt.Println(err)
return
}
if err := binary.Read(&buf, binary.BigEndian, &strBytes); err != nil {
fmt.Println(err)
return
}
fmt.Println(value)
fmt.Println(string(strBytes))
}
encoding/binary包提供了binary.Read和binary.Write函数用于读取和写入二进制数据,这两个函数的第1个参数是实现了io.Reader和io.Writer接口的对象,它可以是文件、网络套接字或是类似上面代码中使用的动态长度内存缓冲区bytes.Buffer;第2个参数是字节序,其中binary.BigEndian表示大端字节序,binary.LittleEndian表示小端字节序;第3个参数就是具体要读取或写入的变量。
注意:我们要知道encoding/binary只能处理定长数据,对于变长数据(如字符串、切片、变长数组等),它不会直接处理,上面代码中我们处理字符串时也是将其转换为了[]byte类型。
除了基本类型,我们也可以使用结构体封装一组数据供encoding/binary包读写,但和之前类似,结构体内部的字段也必须是定长的,下面是一个例子。
package main
import (
"bytes"
"encoding/binary"
"fmt"
)
type Point struct {
X int32
Y int32
}
func main() {
// 创建动态缓冲区
var buf = bytes.Buffer{}
// 要写入的数据
point := Point{X: -3, Y: 15}
// 写入数据
if err := binary.Write(&buf, binary.BigEndian, &point); err != nil {
fmt.Println(err)
return
}
// 读取数据
result := Point{}
if err := binary.Read(&buf, binary.BigEndian, &result); err != nil {
fmt.Println(err)
return
}
fmt.Println(result)
}