1. 序列化和反序列化
1.1 序列化
1.1.1 什么是序列化
序列化它是一种将程序中的数据结构(map、slice、array等)或对象状态转换成一系列字节序列的过程,这些字节可以被存储或通过网络发送。
在GO中,序列化通常涉及到将结构体或其他数据类型转换成JSON、XML、二进制等格式。
1.1.2 为什么要序列化
内存中的map、slice、array以及各种对象,如何保存到一个文件中? 如果是自己定义的结构体的实例,如何保存到一个文件中?
这就需要设计一套协议,按照某种规则,把内存中数据保存到文件中,而文件是一个字节序列,所以必须把数据转换成字节序列,输出到文件,这就是序列化。
序列化的需求主要来自于以下几个方面:
- 数据持久化:我们需要将数据保存到硬盘或数据库中,以便在程序关闭后再次启动时能够恢复数据。
- 网络通信:在分布式系统中,服务之间需要通过网络进行通信,序列化是将数据转换成适合网络传输格式的关键步骤。
- 跨语言交互:不同的编程语言可能有不同的数据结构表示方式,序列化提供了一种通用的方法来确保数据在不同语言之间能够被正确理解和使用。
1.1.3 何时进行序列化
- 数据存储:在将数据写入文件或数据库之前。
- 网络传输:在将数据发送到远程服务之前。
- 数据交换:在不同系统或服务之间交换数据时。
1.1.4 如何进行序列化
- 选择序列化格式:根据需求选择合适的序列化格式,如JSON、XML或二进制。
- 定义数据结构:在GO中定义要序列化的数据结构,通常是通过定义一个或多个结构体。
- 序列化数据:使用GO标准库中的序列化函数,如
json.Marshal
或xml.Marshal
,将数据结构转换成字节流。
1.2 反序列化
反序列化是将序列化的数据(通常是字节流)转换回内存中的数据结构的过程。
1.3 总结
- serialization 序列化:将内存中对象存储下来,把它变成一个个字节。转为二进制数据。
- deserialization 反序列化:将文件的一个个字节恢复成内存中对象。从 二进制 数据中恢复序列化保存到文件就是持久化。
可以将数据序列化后持久化,或者网络传输;也可以将从文件中或者网络接收到的字节序列反序列化。
我们可以把数据和二进制序列之间的相互转换称为二进制序列化、反序列化,把数据和字符序列之间的相互转换称为字符序列化、反序列化。
字符序列化:JSON、XML等。
二进制序列化:Protocol Buffers、MessagePack等。
2. Json(文本序列化)
JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式。它基于1999年发布的ES3 (ECMAScript是w3c组织制定的JavaScript规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。
JSON是什么?字符串!
应该说,目前JSON得到几乎所有浏览器的支持。参看 http://json.org/。
2.1 JSON的数据类型
2.1.1 值
双引号引起来的字符串、数值、true和false、null、对象、数组,这些都是值。
2.1.2 字符串
由双引号包围起来的任意字符的组合,可以有转义字符。
2.1.3 数值
有正负,有整数、浮点数。
2.1.4 对象
无序的键值对的集合,格式: {"key1":value1, ... ,"keyn":valulen},key必须是一个字符串,需要双引号包围这个字符串。 value可以是任意合法的值。
2.1.5 数组
有序的值的集合 格式:[val1,...,valn]。
2.1.6 示例
{
"person": [
{
"name": "tom",
"age": 18
},
{
"name": "jerry",
"age": 16
}
],
"total": 2
}
特别注意:JSON是字符串,是文本。JavaScript引擎可以将这种字符串解析为某类型的数据。
2.2 json包
1. json.Marsha(序列化)
语法:
func json.Marshal(v any) ([]byte, error)
参数:
v any:
v
是一个空接口类型(interface{}
),这意味着你可以传递任何类型的数据结构给它。返回值:
[]byte:字节切片(
[]byte
),包含了 JSON 格式的数据。error:如果序列化过程中出现错误,这个错误会被返回。
2. json.Unmarshal(反序列化)
语法:
func json.Unmarshal(data []byte, v any) error
参数:
data []byte:这是一个包含JSON数据的字节切片。
v any:这是一个空接口类型(
interface{}
),它可以接受任何类型的值。在Unmarshal
函数中,这个参数通常是一个指向Go结构体的指针,或者是你想将JSON数据解码到的其他类型的变量。返回值:
error:如果在解码过程中遇到任何错误,这个错误会被返回。如果没有错误发生,返回值将是
nil
。
2.2.1 基本类型序列化
package main
import (
"encoding/json"
"fmt"
)
func main() {
var data = []any{
// 100: 3个字符
100,
// 2.5: 3个字符
2.5,
// true: 4个字符
true,
// false: 5个字符
false,
// nil(null): 4个字符
nil,
// "accc":6个字符
"accc",
}
// 对切片中的元素挨个进行序列化
for i, v := range data {
b, err := json.Marshal(v)
if err != nil {
continue
}
fmt.Printf("i=%d | v的类型=%T | v的值=%[2]v | b的类型=%[3]T | b的值=%[3]v | string(b)的类型=%[4]T | string(b)的值=%[4]v\n", i, v, b, string(b))
}
// 对整个切片序列化
fmt.Println(json.Marshal(data))
t, _ := json.Marshal(data)
fmt.Println(string(t))
fmt.Printf("%q", string(t)) // 注意,实际上序列化后的json数据,是有双引号的。
}
========调试结果========
i=0 | v的类型=int | v的值=100 | b的类型=[]uint8 | b的值=[49 48 48] | string(b)的类型=string | string(b)的值=100
i=1 | v的类型=float64 | v的值=2.5 | b的类型=[]uint8 | b的值=[50 46 53] | string(b)的类型=string | string(b)的值=2.5
i=2 | v的类型=bool | v的值=true | b的类型=[]uint8 | b的值=[116 114 117 101] | string(b)的类型=string | string(b)的值=true
i=3 | v的类型=bool | v的值=false | b的类型=[]uint8 | b的值=[102 97 108 115 101] | string(b)的类型=string | string(b)的值=false
i=4 | v的类型=<nil> | v的值=<nil> | b的类型=[]uint8 | b的值=[110 117 108 108] | string(b)的类型=string | string(b)的值=null
i=5 | v的类型=string | v的值=accc | b的类型=[]uint8 | b的值=[34 97 99 99 99 34] | string(b)的类型=string | string(b)的值="accc"
[91 49 48 48 44 50 46 53 44 116 114 117 101 44 102 97 108 115 101 44 110 117 108 108 44 34 97 99 99 99 34 93] <nil>
[100,2.5,true,false,null,"accc"]
"[100,2.5,true,false,null,\"accc\"]"
这里说下为什么上面b的值如100是[49 48 48]?
首先json本身的数据类型就是string,表达100的时候,直接就是1 0 0,但不能添加双引号,否则数据类型错误。上面看到的引号,只是在go中的界定符,json中实际不存在,看v的值就知道了。
也就是说100在json中是3个字符,对应的ASCII编码表就是:字符1=49(十进制)、字符0=48(十进制)。
在说下nil,它是一个特殊的数据类型,本意为空,转换为字符串后,就会变成null。
2.2.1.1 存储序列化数据
实际的存储,肯定是输出到文件或者数据库,这里只是演示一下把json数据写入到二维切片。
package main
import (
"encoding/json"
"fmt"
)
func main() {
var data = []any{100, 2.5, true, false, nil, "accc"}
// 定义二维切片,存储json序列化数据
var target = make([][]byte, 0, len(data))
// 对切片中的元素进行序列化
for _, v := range data {
b, err := json.Marshal(v)
if err != nil {
continue
}
target = append(target, b)
// fmt.Printf("i=%d | v的类型=%T | v的值=%[2]v | b的类型=%[3]T | b的值=%[3]v | string(b)的类型=%[4]T | string(b)的值=%[4]v\n", i, v, b, string(b))
}
fmt.Println(target)
}
=====================调试结果=====================
[[49 48 48] [50 46 53] [116 114 117 101] [102 97 108 115 101] [110 117 108 108] [34 97 99 99 99 34]]
2.2.1.2 反序列化
package main
import (
"encoding/json"
"fmt"
)
func main() {
var data = []any{100, 2.5, true, false, nil, "accc"}
var target = make([][]byte, 0, len(data))
for _, v := range data {
b, err := json.Marshal(v)
if err != nil {
continue
}
target = append(target, b)
}
// fmt.Println(target)
fmt.Println("==============反序列化==============")
for i, v := range target {
// json.Unmarshal需要的any类型,反序列化后的数据,也是存在a里面的
var a any
err := json.Unmarshal(v, &a)
if err != nil {
// 如果反序列化失败,则跳过本次循环,继续下一次循环
continue
}
fmt.Printf("%d 反序列化后的值=%[3]v 反序列化后的类型=%[3]T 反序列化前的类型=%[2]T %[2]v \n", i, v, a)
}
}
==============反序列化==============
0 反序列化后的值=100 反序列化后的类型=float64 反序列化前的类型=[]uint8 [49 48 48]
1 反序列化后的值=2.5 反序列化后的类型=float64 反序列化前的类型=[]uint8 [50 46 53]
2 反序列化后的值=true 反序列化后的类型=bool 反序列化前的类型=[]uint8 [116 114 117 101]
3 反序列化后的值=false 反序列化后的类型=bool 反序列化前的类型=[]uint8 [102 97 108 115 101]
4 反序列化后的值=<nil> 反序列化后的类型=<nil> 反序列化前的类型=[]uint8 [110 117 108 108]
5 反序列化后的值=accc 反序列化后的类型=string 反序列化前的类型=[]uint8 [34 97 99 99 99 34]
为什么 100
和 2.5
在反序列化后变成了 float64?
这是因为 JSON 标准中数字默认表示为双精度浮点数(float64
)。即使它们看起来像是整数,Go 语言的 json
包在没有小数点的情况下也会将它们解码为 float64
类型。
2.2.1.3 数组序列化
package main
import (
"encoding/json"
"fmt"
)
func main() {
// var data = []any{100, 2.5, true, false, nil, "accc"}
var data = []any{[...]int{97, 98, 99}}
var target = make([][]byte, 0, len(data))
for _, v := range data {
b, err := json.Marshal(v)
if err != nil {
continue
}
target = append(target, b)
}
// fmt.Println(target)
fmt.Println("==============反序列化==============")
for i, v := range target {
// json.Unmarshal需要的any类型,反序列化后的数据,也是存在a里面的
var a any
err := json.Unmarshal(v, &a)
if err != nil {
// 如果反序列化失败,则跳过本次循环,继续下一次循环
continue
}
fmt.Printf("%d 反序列化前的值=%[2]v 反序列化后的值=%[3]v 反序列化后的类型=%[3]T 反序列化前的类型=%[2]T\n", i, v, a)
}
}
==============反序列化==============
0 反序列化前的值=[91 57 55 44 57 56 44 57 57 93] 反序列化后的值=[97 98 99] 反序列化后的类型=[]interface {} 反序列化前的类型=[]uint8
json [91 57 55 44 57 56 44 57 57 93],实际对应的go中的字符串是:
实际就是json序列化后,如原来的99,字符序列化后就只占用2个字节了,\x39\x39,如果用整数表达,可以用一个字节,那就是0x39。
2.2.2 结构体序列化
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name string
Age int
}
func main() {
var data = Person{"Tom", 32}
fmt.Println("=======序列化=======")
// 序列化
b, err := json.Marshal(data)
if err != nil {
panic(err)
}
// 序列化后,原有的person类型就被json丢弃了,因为json本身没有person类型
fmt.Printf("序列化前的数据类型=%T 序列化前的值=%[1]v\n序列化后的数据类型=%[2]T 序列化后的值=%[2]v\n", data, b)
fmt.Println(string(b))
fmt.Println("=======反序列化=======")
var a Person
err2 := json.Unmarshal(b, &a)
// err2 := json.Unmarshal([]byte(`{"Name":"Tom","Age":32}`), &a)
if err2 != nil {
panic(err2)
}
fmt.Printf("反序列化前的类型=%T 反序列化前的值=%[1]v\n反序列化后的类型=%[2]T 反序列化后的值=%+[2]v\n", b, a)
}
=======序列化=======
序列化前的数据类型=main.Person 序列化前的值={Tom 32}
序列化后的数据类型=[]uint8 序列化后的值=[123 34 78 97 109 101 34 58 34 84 111 109 34 44 34 65 103 101 34 58 51 50 125]
{"Name":"Tom","Age":32}
=======反序列化=======
反序列化前的类型=[]uint8 反序列化前的值=[123 34 78 97 109 101 34 58 34 84 111 109 34 44 34 65 103 101 34 58 51 50 125]
反序列化后的类型=main.Person 反序列化后的值={Name:Tom Age:32}
2.2.3 切片序列化
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name string
Age int
}
func main() {
var data = []Person{
{Name: "AAA", Age: 20},
{Name: "aaa", Age: 32},
}
fmt.Println("=======序列化=======")
// 序列化
b, err := json.Marshal(data)
if err != nil {
panic(err)
}
// 序列化后,原有的person类型就被json丢弃了,因为json本身没有person类型
fmt.Printf("序列化前的数据类型=%T 序列化前的值=%[1]v\n序列化后的数据类型=%[2]T 序列化后的值=%[2]v\n", data, b)
fmt.Println(string(b))
fmt.Println("=======反序列化=======")
var a []Person
err2 := json.Unmarshal(b, &a)
if err2 != nil {
panic(err2)
}
fmt.Printf("反序列化前的类型=%T 反序列化前的值=%[1]v\n反序列化后的类型=%[2]T 反序列化后的值=%+[2]v\n", b, a)
}
=======序列化=======
序列化前的数据类型=[]main.Person 序列化前的值=[{AAA 20} {aaa 32}]
序列化后的数据类型=[]uint8 序列化后的值=[91 123 34 78 97 109 101 34 58 34 65 65 65 34 44 34 65 103 101 34 58 50 48 125 44 123 34 78 97 109 101 34 58 34 97 97 97 34 44 34 65 103 101 34 58 51 50 125 93]
[{"Name":"AAA","Age":20},{"Name":"aaa","Age":32}]
=======反序列化=======
反序列化前的类型=[]uint8 反序列化前的值=[91 123 34 78 97 109 101 34 58 34 65 65 65 34 44 34 65 103 101 34 58 50 48 125 44 123 34 78 97 109 101 34 58 34 97 97 97 34 44 34 65 103 101 34 58 51 50 125 93]
反序列化后的类型=[]main.Person 反序列化后的值=[{Name:AAA Age:20} {Name:aaa Age:32}]
2.2.4 字段标签
结构体的字段可以增加标签tag,序列化、反序列化时使用。
(1)在字段类型后,可以跟反引号引起来的一个标签,用json为key,value用双引号引起来写,key与 value直接使用冒号,这个标签中不要加入多余空格,否则语法错误。
Name string `json:"姓名"`:
这里表示Name序列化后,就在json中显示为“姓名”。
json表示json库使用,双引号内第一个参数用来指定字段转换使用的名称,多个参数使用逗号隔开。
Email string `json:"邮箱,omitempty"`:omitempty为序列化时忽略空值,也就是该字段 不序列化。
空值为false、0、空数组、空切片、空map、空串、nil空指针、nil接口值。空数组、空切片、空串、空map,长度len为0,也就是容器没有元素。
(2)如果使用 - ,该字段将被忽略Name string `json:"-"`,:
序列化后没有该字段,反序列化也不会转换该字段。
Name string `json:"-,"`:
序列化后该字段显示但名为 "-" ,反序列化也会转换该字段。
(3)多标签使用空格间隔Name string `json:"name,omitempty" msgpack:"myname"`
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name string `json:"姓名"`
Age int `json:"年龄"`
Email string `json:"邮箱,omitempty"` //omitempty 选项表示如果 Email 字段为空(即零值),则在 JSON 输出中省略该字段。
}
func main() {
var data = []Person{
{Name: "AAA", Age: 20},
{Name: "aaa", Age: 32},
}
fmt.Println("=======序列化=======")
// 序列化
b, err := json.Marshal(data)
if err != nil {
panic(err)
}
// 序列化后,原有的person类型就被json丢弃了,因为json本身没有person类型
fmt.Printf("序列化前的数据类型=%T 序列化前的值=%[1]v\n序列化后的数据类型=%[2]T 序列化后的值=%[2]v\n", data, b)
fmt.Println(string(b))
fmt.Println("=======反序列化=======")
var a []Person
err2 := json.Unmarshal(b, &a)
if err2 != nil {
panic(err2)
}
fmt.Printf("反序列化前的类型=%T 反序列化前的值=%[1]v\n反序列化后的类型=%[2]T 反序列化后的值=%+[2]v\n", b, a)
}
=======序列化=======
序列化前的数据类型=[]main.Person 序列化前的值=[{AAA 20 } {aaa 32 }]
序列化后的数据类型=[]uint8 序列化后的值=[91 123 34 229 167 147 229 144 141 34 58 34 65 65 65 34 44 34 229 185 180 233 190 132 34 58 50 48 125 44 123 34 229 167 147 229 144 141 34 58 34 97 97 97 34 44 34 229 185 180 233 190 132 34 58 51 50 125 93]
[{"姓名":"AAA","年龄":20},{"姓名":"aaa","年龄":32}]
=======反序列化=======
反序列化前的类型=[]uint8 反序列化前的值=[91 123 34 229 167 147 229 144 141 34 58 34 65 65 65 34 44 34 229 185 180 233 190 132 34 58 50 48 125 44 123 34 229 167 147 229 144 141 34 58 34 97 97 97 34 44 34 229 185 180 233 190 132 34 58 51 50 125 93]
反序列化后的类型=[]main.Person 反序列化后的值=[{Name:AAA Age:20 Email:} {Name:aaa Age:32 Email:}]
3. MessagePack(二进制序列化)
MessagePack是一个基于二进制高效的对象序列化类库,可用于跨语言通信。 它可以像JSON那样,在 许多种语言之间交换结构对象。但是它比JSON更快速也更轻巧。 支持Python、Ruby、Java、C/C++、 Go等众多语言。宣称比Google Protocol Buffers还要快4倍。
安装
go get github.com/vmihailenco/msgpack/v5
基本使用方法和json包类似
package main
import (
"fmt"
"github.com/vmihailenco/msgpack/v5"
)
type Person struct {
Name string `json:"name" msgpack:"myname"`
Age int `json:"age" msgpack:"myage"`
}
func main() {
// 序列化
var data = []Person{
{Name: "Tom", Age: 16},
{Name: "Jerry", Age: 32},
}
b, err := msgpack.Marshal(data) // 方法都和json兼容
if err != nil {
panic(err)
}
fmt.Println(b, len(b), string(b)) // 二进制
// 反序列化
// 知道目标类型
var j []Person
err = msgpack.Unmarshal(b, &j)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%T: %+[1]v\n", j)
}