序列化和反序列化的概念

把对象转换为字节序列的过程称为对象的序列化把字节序列恢复为对象的过程称为对象的反序列化。

对象的序列化主要有两种用途:

1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;

2) 在网络上传送对象的字节序列。

在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。

当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送

Json序列化

Struct 序列化为Json

1
2
3
4
5
type Student struct {
Name string
Age uint8
Address string
}
1
2
3
4
5
6
7
8
9
10
s1 := Student{"张三", 18, "江苏省"}
//开始json序列化
data, err := json.Marshal(s1)
if err != nil {
fmt.Printf("json.marshal failed,err:", err)
return
}
fmt.Printf("%s\n", string(data))

//{"Name":"张三","Age":18,"Address":"江苏省"}

Map序列化为Json

1
2
3
4
5
6
7
var s2 = make(map[string]interface{})
s2["Name"] = "李四"
s2["Age"] = 0
data, _ = json.Marshal(s2)
fmt.Printf("%s \n", data)

//{"Age":0,"Name":"李四"}

Json反序列化

反序列化为Struct

1
2
3
4
5
6
7
8
9
10
var s3 string = `{"Name":"张三", "Age":0, "Address":"江苏省"}`
var stu Student
err = json.Unmarshal([]byte(s3), &stu)
if err != nil {
fmt.Printf("json.Unmarshal failed,err:", err)
return
}
fmt.Printf("%+v \n", stu)

//{Name:张三 Age:0 Address:江苏省}

反序列化为Map

1
2
3
4
5
6
7
//解析为map
var stuMap = make(map[string]interface{})
json.Unmarshal([]byte(s3), &stuMap)

fmt.Printf("%+v \n", stuMap)

//map[Address:江苏省 Age:0 Name:张三]

反序列化为Map有一个好处是,可以区分字段是否存在,还是字段为0,反序列化为struct时,字段不存在,默认为0或””, 无法区分。

有时,我们做接口开发时,需要判断前端是否传递了该字段,就需要用到这种方式。

1
2
3
4
5
6
//0值与字段不存在区分
if _, ok := stuMap["Age"]; ok {
fmt.Println("Age is ", stuMap["Age"])
}else{
fmt.Println("Field of Age is not exist")
}

Protobuff序列化与反序列化

proto.go文件

1
2
3
4
5
6
7
8
type Body struct {
Weight float32 `protobuf:"fixed32,1,opt,name=weight,proto3" json:"weight,omitempty"`
Height float32 `protobuf:"fixed32,2,opt,name=height,json=height,proto3" json:"height,omitempty"`
}

func (m *Body) Reset() { *m = Body{} }
func (m *Body) String() string { return proto.CompactTextString(m) }
func (*Body) ProtoMessage() {}

序列化与反序列

1
2
3
4
5
6
7
8
9
10
var body = Body{Weight: 60, Height: 170}
data, err := proto.Marshal(&body)
if err != nil {
fmt.Println("proto.Marshal err=%s", err)
}
fmt.Println(data)

var b Body
proto.Unmarshal(data, &b)
fmt.Println(&b)

输出

1
2
[13 0 0 112 66 21 0 0 42 67]
weight:60 height:170

Binary

Binary 序列化

1
2
3
4
5
6
s1 := Student{"张三", 18, "江苏省"}
buf := new(bytes.Buffer)
if err := binary.Write(buf, binary.LittleEndian, s1); err != nil {
fmt.Println("binary.Write err=%s", err)
}
fmt.Println(buf)

返回错误

1
invalid type main.Student

原因:如果字段中有不确定大小的类型,如 int,slice,string 等,则会报错

解决办法:

  • int 换成 int32 等固定大小的类型
  • slice 换成类似 [8]byte 这种固定大小
  • 选择其他序列化方式

正确写法

1
2
3
4
5
6
7
8
9
10
11
12
13
 type StudentScore struct{
Math int32
English int32
}

s1 := StudentScore{80, 90}
buf := new(bytes.Buffer)
if err := binary.Write(buf, binary.LittleEndian, s1); err != nil {
fmt.Println("binary.Write err=%s", err)
}
fmt.Println(buf)

//PZ

Binary反序列化

1
2
3
4
5
6
7
8
//反序列化
var s2 StudentScore
if err := binary.Read(buf, binary.LittleEndian, &s2); err != nil {
fmt.Println("binary.Read err=%s", err)
}
fmt.Printf("%+v", s2)

//{Math:80 English:90}

Gob

针对 binary 不能直接使用 string 和 slice 问题,可以使用 gob

Gob序列化

1
2
3
4
5
6
7
8
9
10
s1 := Student{"张三", 18, "江苏省"}

//序列化
var buffer bytes.Buffer
encoder := gob.NewEncoder(&buffer) //创建编码器
err1 := encoder.Encode(&s1) //编码
if err1 != nil {
log.Panic(err1)
}
fmt.Printf("序列化后:%s", buffer)

返回

1
2
3
{2��Student��Name
AgeAddress
��张三 江苏省 %!s(int=0) %!s(bytes.readOp=0)}反序列化后:{Name:张三 Age:18 Address:江苏省}

Gob反序列化

1
2
3
4
5
6
7
8
9
10
11
12
//反序列化
byteEn := buffer.Bytes()

decoder := gob.NewDecoder(bytes.NewReader(byteEn)) //创建解密器
var s2 Student
err2 := decoder.Decode(&s2) //解密
if err2 != nil {
log.Panic(err2)
}
fmt.Printf("反序列化后:%+v \n", s2)

//{Name:张三 Age:18 Address:江苏省}

比较

方式 优点 缺点
binary 性能高 不支持不确定大小类型 int、slice、string
gob 支持多种类型 性能低
json 支持多种类型 性能低于 binary 和 protobuf

| 方式 | 优点 | 缺点 |
| ——– | ——————– | —————————————————— | |
| binary | 性能高 | 不支持不确定大小类型 int、slice、string | |
| gob | 支持多种类型 | 性能低 | |
| json | 支持多种类型 | 性能低于 binary 和 protobuf | |
| protobuf | 支持多种类型,性能高 | 需要单独存放结构,如果结构变动需要重新生成 .pb.go 文件 |