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 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 文件头呢?答案不用,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 格式都可以用于对象的序列化与反序列化,但是其各有优劣。以下列举了一些公认的优缺点。
JSON | XML | |
---|---|---|
优点 | 格式简单,易于读写与解析。传输占用带宽相对小,信噪比高。多语言支持。 | 可以用 .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 |