【Golang】Go 语言 XML 的序列与反序列化实践

Go 语言 XML 的序列与反序列化实践


导读

本文使用 Go 原生支持的包,对 XML 字符串以及 .xml 文件进行序列化与反序列化实践。同时对 Go 语言下的 JSON 序列化反序列化与 XML 的序列化反序列化进行性能测试与比对。

  • 使用到的包是 encoding/xml。详细文档可查看官方中文文档,链接为: https://studygolang.com/pkgdoc

  • Go 语言 JSON 的序列与反序列化博客已经更新:
    【Golang】Go 语言 JSON 的序列化与反序列化实践

  • 请留意代码块中的注释,注释中有表示出一些非常重要的细节!

  • 本文对 XML 的序列化与反序列化使用较基础,尚未对 XML 文件的标签属性等进行解析。若读者有需要,本文起到抛砖引玉的作用,详细的使用参见官方文档。



一. 案例提出

我们以图书馆为原型进行建模。对于一个图书馆用 library.xml 文件进行描述。

<?xml version="1.0"?>
<Library>
	<name>China library</name>
	<location>China</location>
	<isPrivate>false</isPrivate>
	<score>4.6</score>
	<date>
		<open>2020-07-31T14:27:10.035542+08:00</open>
		<close>2020-07-31T14:27:10.035542+08:00</close>
	</date>
	<bookInfo>
		<number>1005</number>
		<book>
			<name>Go 语言 JSON 序列化与反序列化</name>
			<author>fxtack</author>
			<publishTime>2021-07-08T17:44:53.199855+08:00</publishTime>
		</book>
		<book>
			<name>Go 语言 XML 序列化与反序列化</name>
			<author>fxtack</author>
			<publishTime>2021-07-08T17:44:53.199855+08:00</publishTime>
		</book>
	</bookInfo>
</Library>

下表对字段进行解释

字段名类型映射 Go 类型解释
library-Library(自定义结构体)图书馆根标签
name字符串string图书馆名
location字符串string所在地
isPrivate布尔bool是否是私有图书馆
score浮点数float64点评星级
date-Date(自定义结构体)图书馆开馆闭馆
open字符串time.Time开馆时间
close字符串time.Time必馆时间
bookInfo-BookInfo(自定义结构体)图书信息
number整型int图书总数
book-Book(自定义结构体)图书
(book) name字符串string书名
(book) author字符串string作者
(book) publishTime字符串time.Time发布时间

Go 语言的结构体设计如下:
(其中 library.xml 文件中的标签字段名与 Go 结构体字段中的 xml 标签值一一对应)

package main

type Library struct {
	Name      string   	`xml:"name"`
	Location  string   	`xml:"location"`
	IsPrivate bool		`xml:"isPrivate"`
	Score     float64	`xml:"score"`
	Date      Date     	`xml:"date"`
	BookInfo  BookInfo 	`xml:"bookInfo"`

}

type Date struct {
	Open	time.Time	`xml:"open"`
	Close 	time.Time	`xml:"close"`
}

type BookInfo struct {
	Number 		int		`xml:"number"`
	BookList	[]Book 	`xml:"book"`
}

type Book struct {
	Name 		string		`xml:"name"`
	Author		string		`xml:"author"`
	PublishTime	time.Time	`xml:"publishTime"`
}

基于上述案例与模型进行序列化与反序列化。


一. XML 的序列化

1. 序列化的简单示例

在进行序列化对象为 .xml 文件之前,我们先创建一个 library 对象。

package main

import "time"

var library = Library{
	Name:      "一个图书馆",
	Location:  "中国",
	IsPrivate: true,
	Score:     4.9,
	Date: Date{
		Open:  time.Now(),
		Close: time.Now(),
	},
	BookInfo: BookInfo{
		Number: 1005,
		BookList: []Book{
			{
				Name: "Go 语言 JSON 序列化与反序列化",
				Author: "fxtack",
				PublishTime: time.Now(),
			},
			{
				Name:        "Go 语言 XML 序列化与反序列化",
				Author:      "fxtack",
				PublishTime: time.Now(),
			},
		},
	},
}

通过序列化我们可以将该对象变为一个 XML 字符串,并简单的打印输出:

package main

import (
	"encoding/xml"
	"fmt"
	"log"
)

func main(){

	// 序列化同在 main 包下的 library
	result, err := xml.Marshal(&library)
	if err != nil {
		log.Println("对象序列化为 XML 字符串失败")
	}
	// result 是字节切片类型,要转为字符串再输出
	fmt.Println(string(result))
}

输出结果如下。

<Library><name>广东大图书馆</name><location>广东</location><isPrivate>false</isPrivate><score>4.6</score><date><open>2021-07-08T18:26:33.7720883+08:00</open><close>2021-07-08T18:26:33.7720883+08:00</close></date><bookInfo><number>1005</number><book><name>Go 语言 JSON 序列化与反序列化</name><author>fxtack</author><publishTime>2021-07-08T18:26:33.7720883+08:00</publishTime></book><book><name>Go 语言 XML 序列化与反序列化</name><author>fxtack</author><publishTime>2021-07-08T18:26:33.7720883+08:00</publishTime></book></bookInfo></Library>

很明显这个输出结果的格式太难看了,很长一条。Go 也有考虑到这个问题,因此我们可以使用 xml.MarshalIndent 方法来解决格式化输出的问题。

package main

import (
	"encoding/xml"
	"fmt"
	"log"
)

func main(){
	// 使用了 xml.MarshalIndent 来进行格式序列化
	// 第二个参数为 xml 每行的前缀
	// 第三个参数为标签嵌套时使用的空位符,这里选择用缩进值,也可以使用四个空格
	result, err := xml.MarshalIndent(&library, "", "\t")
	if err != nil {
		log.Println("对象序列化为 XML 字符串失败")
	}
	fmt.Println(string(result))
}

此时的输出就非常规范易读了。

<Library>
	<name>广东大图书馆</name>
	<location>广东</location>
	<isPrivate>false</isPrivate>
	<score>4.6</score>
	<date>
		<open>2021-07-08T18:34:52.9834418+08:00</open>
		<close>2021-07-08T18:34:52.9834418+08:00</close>
	</date>
	<bookInfo>
		<number>1005</number>
		<book>
			<name>Go 语言 JSON 序列化与反序列化</name>
			<author>fxtack</author>
			<publishTime>2021-07-08T18:34:52.9834418+08:00</publishTime>
		</book>
		<book>
			<name>Go 语言 XML 序列化与反序列化</name>
			<author>fxtack</author>
			<publishTime>2021-07-08T18:34:52.9834418+08:00</publishTime>
		</book>
	</bookInfo>
</Library>

注意


无论是 xml.Marshal 还是 xml.MarshalIndent 方法,其传入的第一个参数值类型是 interface{} 类型,也就是用于进行序列化的对象。传入的可以是对象,也可以是对象的地址,以下代码的效果是相同的(以 xml.Marshal 为例),但是建议传入地址。

...
result, err := xml.Marshal(library)
...
...
result, err := xml.Marshal(&library)
...

2. 序列化的完整代码

完整的序列化过程需要对象序列化后输出为 .xml 文件。其流程如下:

构建序列化对象的结构体
由结构体创建对象
序列化得到 XML 格式字符串
写入 .xml 文件

其中,有一个重要的细节,.xml 文件开头有一个头: <?xml version="1.0"?>。这个头在序列化的结果中是没有的,所以得自己写入。

以下为完整代码:

package main

import (
	"encoding/xml"
	"log"
	"os"
)

// library 对象同在 main 包下,详情见上文
func main() {

	// 输出为当前项目目录下的 library.xml
	WriteXML("./library.xml", &library)
}

func WriteXML(filePath string, ref interface{}) {

	// 序列化
	data, err := xml.MarshalIndent(ref, "", "\t")
	if err != nil {
		log.Println("文件序列化错误")
	}
	
	// 拼接 xml 文件头到 xml 字符串前
	data = append([]byte("<?xml version=\"1.0\"?>\n"), data ...)

	// 写出文件
	err = os.WriteFile(filePath, data, 0666)
	if err != nil {
		log.Println("文件写入错误")
	}
}

测试运行后在项目目录下出现 library.xml 并查看,发现 XML 的头也接上了,内容也没有问题。


二. XML 的反序列化

1. 反序列化的简单示例

以上文简单序列化过程中得到的字符串来进行反序列化测试。代码如下:

package main

import (
	"encoding/xml"
	"fmt"
	"log"
)

func main() {

	// 准备接受反序列化的空对象
	var library Library
	// 反序列化的 xml 字符串
	xmlStr := "<?xml version=\"1.0\"?><Library><name>广东大图书馆</name><location>广东</location>" +
		"<isPrivate>false</isPrivate><score>4.6</score><date>" +
		"<open>2021-07-08T18:26:33.7720883+08:00</open>" +
		"<close>2021-07-08T18:26:33.7720883+08:00</close></date>" +
		"<bookInfo><number>1005</number><book><name>Go 语言 JSON 序列化与反序列化</name>" +
		"<author>fxtack</author><publishTime>2021-07-08T18:26:33.7720883+08:00</publishTime>" +
		"</book><book><name>Go 语言 XML 序列化与反序列化</name><author>fxtack</author><publishTime>" +
		"2021-07-08T18:26:33.7720883+08:00</publishTime></book></bookInfo></Library>"

	// 反序列化
	err := xml.Unmarshal([]byte(xmlStr), &library)
	if err != nil {
		log.Println("反序列化失败")
	}
	fmt.Println(library)
}

输出如下,可见反序列化成功。

{广东大图书馆 广东 false 4.6 {2021-07-08 18:26:33.7720883 +0800 CST 2021-07-08 18:26:33.7720883 +0800 CST} {1005 [{Go 语言 JSON 序列化与反序列化 fxtack 2021-07-08 18:26:33.7720883 +0800 CST} {Go 语言 XML 序列化与反序列化 fxtack 2021-07-08 18:26:33.7720883 +0800 CST}]}}

注意


反序列化的函数 xml.Unmarshal 和序列化的函数不同,其传入的第二个参数必须是用于接受结果的结构体对象的地址。即,如下代码第一段是正确的,第二段是错误的。

...
// 正确
err := xml.Unmarshal([]byte(xmlStr), &library)
...
...
// 错误,会产生 err
err := xml.Unmarshal([]byte(xmlStr), library)
...

2. 反序列化的完整代码

反序列化的完整代码要读取 .xml 文件,将读取的内容进行反序列化,得到一个对象。流程如下:

读取 .xml 文件得到 XML 字符串
创建可寻址的结构体对象实例
反序列化并由对象实例接收结果
使用对象实例

注意,序列化过程中要添加 XML 文件头,那反序列化过程中要不要专门处理 XML 文件头呢?答案不用,Go 反序列化过程会自动忽略 XML 文件头。

使用上文序列化产生的 library.xml 进行反序列化。

package main

import (
	"encoding/xml"
	"fmt"
	"log"
	"os"
)

func main() {
	// 用于接收结果
	var library Library
	ReadXML("./library.xml", &library)
	fmt.Println(library)
}

func ReadXML(filePath string, ref interface{}) {
	// 读取文件
	f, err := os.ReadFile(filePath)
	if err != nil {
		log.Println("读取错误")
		return
	}
	
	// 反序列化
	err = xml.Unmarshal(f, ref)
	if err != nil {
		log.Println("反序列化失败")
		return
	}
}

输出结果如下,反序列化成功。

{Shanghai library Shanghai false 4.6 {2020-07-31 14:27:10.035542 +0800 CST 2020-07-31 14:27:10.035542 +0800 CST} {1005 [{Go 语言 JSON 序列化与反序列化 fxtack 2021-07-08 19:13:06.6253163 +0800 CST} {Go 语言 XML 序列化与反序列化 fxtack 2021-07-08 19:13:06.6253163 +0800 CST}]}}

三. Go 语言下 JSON 与 XML 序列化反序列化性能测试

JSON 格式和 XML 格式都可以用于对象的序列化与反序列化,但是其各有优劣。以下列举了一些公认的优缺点。

JSONXML
优点格式简单,易于读写与解析。传输占用带宽相对小,信噪比高。多语言支持。可以用 .dtd 或 xml Schema 规范编辑格式。数据表示方式更为多样。方便传输
缺点相对 xml 规范性弱。信噪比低。解析速度比 JSON 慢,解析更复杂。

提示


Go 中 JSON 的序列化与反序列化可以参照上一篇博客【Golang】Go 语言 JSON 的序列化与反序列化实践

对于同样的 Go 对象,我们对其进行 JSON 序列化与反序列化,和 XML 序列化与反序列化。简单的比较一下两者的性能高低。写了两个基准测试:

序列化基准测试:

package performance

import (
	"encoding/json"
	"encoding/xml"
	"log"
	"os"
	"testing"
)

func Benchmark_TestJSONMarshal(b *testing.B) {
	var data []byte
	for i := 0 ; i < b.N ; i++ {
		data, _ = json.Marshal(library)
	}


	err := os.WriteFile("./library2.json", data, 0666)
	if err != nil {
		log.Println("文件写入错误")
	}
}

func Benchmark_TestXMLMarshal(b *testing.B) {
	var data []byte
	for i := 0 ; i < b.N ; i++ {
		data, _ = xml.Marshal(library)
	}

	err := os.WriteFile("./library2.xml", data, 0666)
	if err != nil {
		log.Println("文件写入错误")
	}
}

反序列基准测试:

package performance

import (
	"encoding/json"
	"encoding/xml"
	"log"
	"os"
	"testing"
)

func Benchmark_TestJSONUnmarshal(b *testing.B) {
	var ref Library
	f, err := os.ReadFile("./library.json")
	if err != nil {
		log.Println("读取错误")
		return
	}
	for i := 0 ; i < b.N ; i++ {
		_ = json.Unmarshal(f, &ref)
	}
}

func Benchmark_TestXMLUnmarshal(b *testing.B) {
	var ref Library
	f, err := os.ReadFile("./library.xml")
	if err != nil {
		log.Println("读取错误")
		return
	}
	for i := 0 ; i < b.N ; i++ {
		_ = xml.Unmarshal(f, &ref)
	}
}

两个测试中用的 Library 结构体以及结构体对象都是同一个如下:

package performance

import "time"

type Library struct {
	Name      string   	`xml:"name",json:"name"`
	Location  string   	`xml:"location",json:"location"`
	IsPrivate bool		`xml:"isPrivate",json:"isPrivate"`
	Score     float64	`xml:"score",json:"score"`
	Date      Date     	`xml:"date",json:"date"`
	BookInfo  BookInfo 	`xml:"bookInfo",json:"bookInfo"`

}

type Date struct {
	Open	time.Time	`xml:"open",json:"open"`
	Close 	time.Time	`xml:"close",json:"close"`
}

type BookInfo struct {
	Number 		int		`xml:"number",json:"number"`
	BookList	[]Book 	`xml:"book",json:"bookList"`
}

type Book struct {
	Name 		string		`xml:"name",json:"name"`
	Author		string		`xml:"author",json:"author"`
	PublishTime	time.Time	`xml:"publishTime",json:"publishTime"`
}

var library = Library{
	Name:      "大大大图书馆",
	Location:  "广东",
	IsPrivate: false,
	Score:     5.0,
	Date: Date{
		Open:  time.Now(),
		Close: time.Now(),
	},
	BookInfo: BookInfo{
		Number: 1005,
		BookList: []Book{
			{
				Name: "Go 语言 JSON 序列化与反序列化",
				Author: "fxtack",
				PublishTime: time.Now(),
			},
			{
				Name:        "Go 语言 XML 序列化与反序列化",
				Author:      "fxtack",
				PublishTime: time.Now(),
			},
			{
				Name:        "Go 语言序列化与反序列化",
				Author:      "fxtack",
				PublishTime: time.Now(),
			},
		},
	},
}

在 performance 包下,启动控制台输入以下指令启动 Go 基准测试。

$ go test -v -bench=. -benchmem

测试结果如下:

Benchmark_TestJSONMarshal 测试方法的执行为例进行解释

  • Benchmark_TestJSONMarshal-12 中的 12 表示 12 个线程执行
  • 68574 表示测试循环执行了 68574 次
  • 17389 ns/op 表示平均每次执行耗时为 17389 纳秒
  • 3782 B/op 表示平均每次执行使用内存 3782 B
  • 22 allocs/op 表示每次执行分配了多少个对象

我们可以明显的看到 JSON 无论序列化还是反序列化,其执行速度都比 XML 的序列化与反序列化快,内存消耗少。

并且我们也可以发现,反序列化比序列化性能消耗更高。


四. 使用 io 流进行反序列化与序列化

假想,我们需要对一个大 XML 文件进行反序列化。该文件过大,无法一次性读取到内存中进行反序列化,那应该怎么办呢?Go 语言中提供了针对 io 流的序列化与方序类化函数。

以下示例展示了如何使用 io.Reader 来进行反序列化。

package main

import (
	"encoding/xml"
	"log"
	"os"
)

func main() {

	// 以只读模式打开大 XML 文件
	file, err := os.OpenFile("library.xml", os.O_RDONLY, 0777)
	if err != nil {
		log.Fatalln(err)
	}

	// 结果接收对象
	var lib Library
	
	// 使用 io.Reader 创建 decoder 
	decoder := xml.NewDecoder(file)

	// 使用 decoder 进行反序列化
	err = decoder.Decode(&lib)
	if err != nil {
		log.Fatalln(err)
	}
}

提示


Go 中需要对 HTTP 请求的 body 内容进行反序列化时,就常常使用以上方法。这样可以避免 body 过大,从而减少内存占用。

相似的,如果对一个巨大的对象进行序列化,其序列化后的内容可能会过大而导致内存溢出,则可以使用 io.Writer 来进行序列化。

以下示例展示了如何使用 io.Writer 来进行序列化。

package main

import (
	"encoding/xml"
	"log"
	"os"
)

func main() {

	// 以只写模式打开当前目录下的 XML 大文件
	file, err := os.OpenFile("library.xml", os.O_WRONLY, 0777)
	if err != nil {
		log.Fatalln(err)
	}
	// 使用 io.Writer 创建 encoder
	encoder := xml.NewEncoder(file)
	
	// 使用 encoder 进行序列化
	err = encoder.Encode(&library)
	if err != nil {
		log.Fatalln(err)
	}
}

以下示例展示了如何使用 io.Writer 进行规范序列化。

package main

import (
	"encoding/xml"
	"log"
	"os"
)

func main() {
	file, err := os.OpenFile("hello.xml", os.O_WRONLY, 0777)
	if err != nil {
		log.Fatalln(err)
	}
	encoder := xml.NewEncoder(file)

	// 该语句进行规范化设置
	// 相当于设置 xml.MarshalIndent(&library, "", "\t") 中的后两个参数
	encoder.Indent(" ", "\t")
	
	err = encoder.Encode(&library)
	if err != nil {
		log.Fatalln(err)
	}
}

其他相关文章

文章名称更新时间
Go 语言 JSON 的序列化与反序列化实践2021-9-27
Go 语言 XML 的序列化与反序列化实践2021-7-08

文章内容来自个人学习总结
欢迎指出本文中存在的问题
未经本人同意禁止转载,不得用于商业用途
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值