当前位置: 首页 > news >正文

Go 1.19.4 序列化和反序列化-Day 16

1. 序列化和反序列化

1.1 序列化

1.1.1 什么是序列化

序列化它是一种将程序中的数据结构(map、slice、array等)或对象状态转换成一系列字节序列的过程,这些字节可以被存储或通过网络发送。

在GO中,序列化通常涉及到将结构体或其他数据类型转换成JSON、XML、二进制等格式。

1.1.2 为什么要序列化

内存中的map、slice、array以及各种对象,如何保存到一个文件中? 如果是自己定义的结构体的实例,如何保存到一个文件中?

这就需要设计一套协议,按照某种规则,把内存中数据保存到文件中,而文件是一个字节序列,所以必须把数据转换成字节序列,输出到文件,这就是序列化。 

序列化的需求主要来自于以下几个方面:

  1. 数据持久化:我们需要将数据保存到硬盘或数据库中,以便在程序关闭后再次启动时能够恢复数据。
  2. 网络通信:在分布式系统中,服务之间需要通过网络进行通信,序列化是将数据转换成适合网络传输格式的关键步骤。
  3. 跨语言交互:不同的编程语言可能有不同的数据结构表示方式,序列化提供了一种通用的方法来确保数据在不同语言之间能够被正确理解和使用。

 1.1.3 何时进行序列化

  1. 数据存储:在将数据写入文件或数据库之前。
  2. 网络传输:在将数据发送到远程服务之前。
  3. 数据交换:在不同系统或服务之间交换数据时。

1.1.4 如何进行序列化

  1. 选择序列化格式:根据需求选择合适的序列化格式,如JSON、XML或二进制。
  2. 定义数据结构:在GO中定义要序列化的数据结构,通常是通过定义一个或多个结构体。
  3. 序列化数据:使用GO标准库中的序列化函数,如json.Marshalxml.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 mainimport ("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 mainimport ("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 mainimport ("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 anyerr := 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]

为什么 1002.5 在反序列化后变成了 float64?

这是因为 JSON 标准中数字默认表示为双精度浮点数(float64)。即使它们看起来像是整数,Go 语言的 json 包在没有小数点的情况下也会将它们解码为 float64 类型。

2.2.1.3 数组序列化
package mainimport ("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 anyerr := 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 mainimport ("encoding/json""fmt"
)type Person struct {Name stringAge  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 Personerr2 := 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 mainimport ("encoding/json""fmt"
)type Person struct {Name stringAge  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 []Personerr2 := 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 mainimport ("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 []Personerr2 := 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 mainimport ("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 []Personerr = msgpack.Unmarshal(b, &j)if err != nil {fmt.Println(err)return}fmt.Printf("%T: %+[1]v\n", j)
}

相关文章:

  • 速盾:视频开cdn合适还是视频点播合适?
  • 大模型智能体在金融公告理解领域的应用 | OPENAIGC开发者大赛高校组AI创新之星奖
  • 语音音频(wav)声纹识别-技术实现-python
  • 【JavaEE初阶】网络原理
  • 性能优化与资源管理:优化Selenium脚本的执行效率,合理管理浏览器实例和系统资源
  • CSS给一行按钮统一设置间隔
  • DarkLabel2.4版本导入MOT17数据集
  • 如何解决跨境电商税务管理难题
  • Android常用C++特性之lambda表达式
  • 2-107 基于matlab的hsv空间双边滤波去雾图像增强算法
  • Linux 简易shell编写
  • ResNet18果蔬图像识别分类
  • Git提示信息 Pulling is not possible because you have unmerged files.
  • 线段树查询区间回文+区间字母右移
  • Python NumPy 标准数据生成:高效创建与操作数组
  • ----------
  • python3.6+scrapy+mysql 爬虫实战
  • 「前端早读君006」移动开发必备:那些玩转H5的小技巧
  • 【407天】跃迁之路——程序员高效学习方法论探索系列(实验阶段164-2018.03.19)...
  • CSS 提示工具(Tooltip)
  • eclipse的离线汉化
  • Javascript编码规范
  • learning koa2.x
  • MySQL用户中的%到底包不包括localhost?
  • Puppeteer:浏览器控制器
  • python 学习笔记 - Queue Pipes,进程间通讯
  • Storybook 5.0正式发布:有史以来变化最大的版本\n
  • 从零开始的无人驾驶 1
  • 第十八天-企业应用架构模式-基本模式
  • 爬虫模拟登陆 SegmentFault
  • 首页查询功能的一次实现过程
  • 算法系列——算法入门之递归分而治之思想的实现
  • ionic入门之数据绑定显示-1
  • PostgreSQL 快速给指定表每个字段创建索引 - 1
  • ​香农与信息论三大定律
  • # 数仓建模:如何构建主题宽表模型?
  • (+3)1.3敏捷宣言与敏捷过程的特点
  • (04)odoo视图操作
  • (3)nginx 配置(nginx.conf)
  • (LeetCode C++)盛最多水的容器
  • (动手学习深度学习)第13章 计算机视觉---微调
  • (附源码)node.js知识分享网站 毕业设计 202038
  • (附源码)计算机毕业设计ssm基于Internet快递柜管理系统
  • (每日持续更新)jdk api之FileFilter基础、应用、实战
  • (十一)c52学习之旅-动态数码管
  • ******之网络***——物理***
  • ***测试-HTTP方法
  • .NET 4.0网络开发入门之旅-- 我在“网” 中央(下)
  • .NET CF命令行调试器MDbg入门(三) 进程控制
  • .Net Core缓存组件(MemoryCache)源码解析
  • .NET 中的轻量级线程安全
  • .NET轻量级ORM组件Dapper葵花宝典
  • .NET设计模式(11):组合模式(Composite Pattern)
  • /dev/VolGroup00/LogVol00:unexpected inconsistency;run fsck manually
  • @ 代码随想录算法训练营第8周(C语言)|Day53(动态规划)