• Go语言中的数据格式(json、xml 、msgpack、protobuf)


     在分布式的系统中,因为涉及到数据的传输,所以一定会进行数据的交换,此时就要定义数据交换的格式,例如二进制、Json、Xml等等。本篇文章就是总结一下常用的几种数据格式。

     一、Json格式

    如果想使用Json数据格式,可以借助于encoding/json这个包。

    利用json包里的 json.Marshal(xxx) 和 json.Unmarshal(data, &xxx) 进行序列化和反序列化。

     下面举个例子:

    package main
    
    import (
        "encoding/json"
        "fmt"
        "io/ioutil"
        "math/rand"
    )
    
    type Student struct {
        Name string
        Age  int
        Sex  string
    }
    
    //写入json数据
    func writeJson(filename string) (err error) {
        var students []*Student
        //随机生成10个学生数据
        for i := 0; i < 10; i++ {
            p := &Student{
                Name: fmt.Sprintf("name%d", i),
                Age:  rand.Intn(100),
                Sex:  "Man",
            }
    
            students = append(students, p)
        }
    
        //执行序列化操作
        data, err := json.Marshal(students)
        if err != nil {
            fmt.Printf("=marshal failed, err:%v
    ", err)
            return
        }
    
        //将数据写到一个文件当中
        err = ioutil.WriteFile(filename, data, 0755)
        if err != nil {
            fmt.Printf("write file failed, err:%v
    ", err)
            return
        }
    
        return
    }
    
    //读取json数据
    func readJson(filename string) (err error) {
        var students []*Student
        data, err := ioutil.ReadFile(filename)
        if err != nil {
            return
        }
    
        err = json.Unmarshal(data, &students)
        if err != nil {
            return
        }
    
        for _, v := range students {
            fmt.Printf("%#v
    ", v)
        }
        return
    }

     执行:

    func main() {
        filename := "C:/tmp/Students.txt"
        err := writeJson(filename)
        if err != nil {
            fmt.Printf("write json failed, err:%v
    ", err)
            return
        }
    
        err = readJson(filename)
        if err != nil {
            fmt.Printf("read json failed, err:%v
    ", err)
            return
        }
    }

    执行结果:

    1.可以看到在C:/tmp/下面生成了一个Students.txt文件,打开里面存放是刚刚随机生成的10个学生数据

    2.执行结果可以看到控制台打印:

    二、Xml格式

    Xml格式也是我们常用的数据格式,同样要使用Xml格式,可以使用encoding/xml这个包。

    像上面json一样,同样存在 xml.Marshal(xxx) 和 xml.Unmarshal(data, &xxx) 两个方法。此外还有方法xml.MarshalIndent(xxx) 可以格式化xml

    先熟悉一下XML对应 标签怎么写:

    - XMLName字段,如上所述,会省略

    - 具有标签"-"的字段会省略

    - 具有标签"name,attr"的字段会成为该XML元素的名为name的属性

    - 具有标签",attr"的字段会成为该XML元素的名为字段名的属性

    - 具有标签",chardata"的字段会作为字符数据写入,而非XML元素

    - 具有标签",innerxml"的字段会原样写入,而不会经过正常的序列化过程

    - 具有标签",comment"的字段作为XML注释写入,而不经过正常的序列化过程,该字段内不能有"--"字符串

    - 标签中包含"omitempty"选项的字段如果为空值会省略

      空值为false、0、nil指针、nil接口、长度为0的数组、切片、映射

    - 匿名字段(其标签无效)会被处理为其字段是外层结构体的字段

    - 如果一个字段的标签为"a>b>c",则元素c将会嵌套进其上层元素a和b中。如果该字段相邻的字段标签指定了同样的上层元素,则会放在同一个XML元素里。

    原文链接:https://blog.csdn.net/yuyinghua0302/article/details/84568531

    下面举个例子:

    例如我想创建一个如下的xml数据:

    <Servers version="2.0">
        <server>
            <serverName>Server0</serverName>
            <serverIP>192.168.1.0</serverIP>
        </server>
        <server>
            <serverName>Server1</serverName>
            <serverIP>192.168.1.1</serverIP>
        </server>
    </Servers>
    

    我就可以创建下面这样的结构体:

    //最外层的xml
    type Servers struct {
        XMLName xml.Name  `xml:"Servers"`
        Version string    `xml:"version,attr"`
        Servers []*Server `xml:"server"`
    }
    
    //具体的server
    type Server struct {
        ServerName string `xml:"serverName"`
        ServerIP   string `xml:"serverIP"`
    }

    写文件方法:

    func writeXml(fileName string) (err error) {
        //创建一个*Server类型的数组
        var serverList []*Server
        for i := 0; i < 2; i++ {
            s := &Server{
                ServerName: fmt.Sprintf("Server%d", i),
                ServerIP:   fmt.Sprintf("192.168.1.%d", i),
            }
            serverList = append(serverList, s)
        }
    
        var myServers *Servers = &Servers{
            Version: "2.0",
            Servers: serverList,
        }
    
        //执行序列化操作
        data, err := xml.MarshalIndent(myServers, "", "    ")
        if err != nil {
            fmt.Printf("=marshal failed, err:%v
    ", err)
            return
        }
    
        //将数据写到一个文件当中
        err = ioutil.WriteFile(fileName, data, 0755)
        if err != nil {
            fmt.Printf("write file failed, err:%v
    ", err)
            return
        }
    
        return
    }

    如上代码,使用了MarshalIndent方法,第一个参数是需要序列化的数据,第二参数是前缀,第三个是缩进的字符串(这里是四个空格),然后在main方法中调用一下即可(代码略)。

    这里主要想说明一下结构体里面的标签:

    XmlName可以省略不写,不写的话最外层就是用的结构体的名称,例如第一个结构体是Servers,那么xml最外层的节点名称就是Servers。

    读的话,使用 xml.Unmarshal(data, &xxx) 就可以实现了。

    func readXml(fileName string) (err error) {
        var myServers *Servers
        data, err := ioutil.ReadFile(fileName)
        if err != nil {
            return
        }
    
        err = xml.Unmarshal(data, &myServers)
        if err != nil {
            return
        }
    
        fmt.Printf("XMLNAME = %v
    ", myServers.XMLName)
        fmt.Printf("Version = %v
    ", myServers.Version)
        for _, v := range myServers.Servers {
            fmt.Printf("%v
    ", v)
        }
        return
    }

    三、msgPack格式

    上面两种Json和Xml格式,都是文本格式的数据,好处在于能够方便的阅读。但是问题在于占用空间比较大。所以又出现了MsgPack这种格式,它是在json基础上转换为二进制进行传输的。对应关系像下面这个图:

    MsgPack并没有官方的包,我们需要使用一个第三方的包,项目地址:https://github.com/vmihailenco/msgpack  

    实现比较简单,将 json.Marshal 和 json.Unmarshal 中的【 json】替换为【 maspack】即可,下面是对上面代码的改造,创建了10000个学生的数据。

     

    四、protobuf格式

    protobuf是Google公司开发出的一种数据格式。官方文档地址:https://developers.google.cn/protocol-buffers/

    简单讲它使用了IDL语言作为中间语言来串联不同的编程语言。不同的语言可以根据生成的IDL中间语言,生成自己的语言。

    这样做有什么好处? 举个例子:当我们在协作开发的时候,A部门使用的是Go语言、B部分使用的是Java语言,C部门使用的是C#语言,当他们之间进行数据交换的时候,都要各自维护自己的结构体,才能进行数据的

    序列化和反序列化,使用protobuf的好处就是只需要一个IDL描述,然后生成不同的语言的结构,这样维护一份就可以了。

    同时 prototbuf的性能也很好,这也是它的一个优势。IDL语言使用的变长编码(根据整数的范围 0-255 那么这个数字就占用1个字节 ,如果使用定长编码的话 一个整数可能就是 4个字节)所以它的空间利用率是很好的。

    那开发流程是怎样的?

    A. IDL编写

    B. 生成只定语言的代码

    C. 序列化和反序列化

    如何在Go中应用prototbuf

    A.安装protoc编译器,解压后拷贝到GOPATH/bin目录下, 下载地址:https://github.com/google/protobuf/releases

     

    然后把bin下面的protoc.exe 这个放到GoPath下的bin中,打开cmd,输入protoc,应该会出现如下内容:

     

    如果不存在,可以将Gopath的bin加入到系统的环境变量path当中。

    B.安装生成Go语言的插件

    执行命令:go get -u github.com/golang/protobuf/protoc-gen-go

    C. 创建一个简单的proto文件 

    //指定版本
    //注意proto3与proto2的写法有些不同
    syntax = "proto3";
    
    //包名,通过protoc生成时go文件时
    package school;
    
    //性别
    //枚举类型第一个字段必须为0
    enum Sex {
        male = 0;
        female = 1;
        other =2;
    }
    
    //学生
    message Student {
        Sex sex = 1;
        string Name = 2;
        int32 Age =3;
    }
    
    //班级
    message Class{
        repeated Student Students =1;
        string Name; 
    }

      message 就可以理解成类, repeated可以理解成数组。

    D.利用之前下载好的protoc.exe 生成一个Go的代码。 第一个【.】代表当前输出的目录,后面*.proto则是 proto文件的路径

    protoc--go_out=.  *.proto

    protoc --go_out=.school .school.proto

    执行之后会生成如下的文件,这个go文件就可以直接使用了。

     

    E. 使用生成的Go文件

    ①使用 proto.Marshal() 执行序列化

    func writeProto(filename string) (err error) {
        //创建学生信息
        var students []*school.Student
        for i := 0; i < 30; i++ {
    
            var sex = (school.Sex)(i % 3)
            student := &school.Student{
                Name: fmt.Sprintf("Student_%d", i),
                Age:  15,
                Sex:  sex,
            }
    
            students = append(students, student)
        }
    
        //创建班级信息
        var myClass school.Class
        myClass.Name = "我的班级"
        myClass.Students = students
    
        data, err := proto.Marshal(&myClass)
        if err != nil {
            fmt.Printf("marshal proto buf failed, err:%v
    ", err)
            return
        }
    
        err = ioutil.WriteFile(filename, data, 0755)
        if err != nil {
            fmt.Printf("write file failed, err:%v
    ", err)
            return
        }
        return
    }

    ②使用proto.Unmarshal(data, &mySchool)执行反序列化

    func readProto(filename string) (err error) {
        var mySchool school.Class
        data, err := ioutil.ReadFile(filename)
        if err != nil {
            return
        }
        err = proto.Unmarshal(data, &mySchool)
        if err != nil {
            return
        }
    
        fmt.Printf("proto:%v
    ", mySchool)
        return
    }

    Q&A

    如果在使用protobuf生成的Go文件,出现了如下的异常:

    undefined: proto.ProtoPackageIsVersion3

    这个时候可能是由于上面两步下载的protoc.exe 和 protobuf 的版本不一致导致的。

    1. 可以清空下gopath下的 github.comgolangprotobuf 然后重新下载,并在github.comgolangprotobufprotoc-gen-go 执行 go install 命令。

    2. 检查一下是不是使用了 godep 等包管理工具,里面引用的版本和protoc.exe 不一致造成的

  • 相关阅读:
    Mac下django简单安装配置步骤
    NuGet 使用笔记
    gulp es7配置文件
    HaProxy配置
    Java工作环境笔记
    ReactJs笔记
    架构应该解决好对象的克隆问题
    Kotlin笔记
    Scala 笔记
    spark 笔记
  • 原文地址:https://www.cnblogs.com/dcz2015/p/11342168.html
Copyright © 2020-2023  润新知