Golang 序列化方式及对比

Golang 序列化的方式:

  • Binary
  • Gob
  • JSON
  • Protobuf

一. Binary

// OK
type Message struct {
    Id   uint64
    Size uint64
}

// Wrong
/*type Message struct {
    Id   int
    Size int
    Data string
}*/

func BinaryRW() {
    m1 := Message{1, 1024}
    buf := new(bytes.Buffer)

    if err := binary.Write(buf, binary.LittleEndian, m1); err != nil {
        log.Fatal("binary write error:", err)
    }

    var m2 Message
    if err := binary.Read(buf, binary.LittleEndian, &m2); err != nil {
        log.Fatal("binary read error:", err)
    }
}

注意: 如果字段中有不确定大小的类型,如 int,slice,string 等,则会报错。
binary write error:binary.Write: invalid type main.Message

下面是binary.Write的函数说明:

// Data must be a fixed-size value or a slice of fixed-size values.
func Write(w io.Writer, order ByteOrder, data interface{}) error {

解决办法:

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

二. Gob

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

type Message2 struct {
    Id   uint64
    Size uint64
    Data string
}

func GobEncodeDecode() {
    m1 := Message2{2, 1024, "gob"}
    var buf bytes.Buffer

    enc := gob.NewEncoder(&buf)
    dec := gob.NewDecoder(&buf)

    if err := enc.Encode(m1); err != nil {
        log.Fatal("encode error:", err)
    }

    var m2 Message2
    if err := dec.Decode(&m2); err != nil {
        log.Fatal("decode error:", err)
    }
}

三. JSON

还可以使用 json 传递数据

type Message2 struct {
    Id   uint64 `json:"id"`
    Size uint64 `json:"size"`
    Data string `json:"data"`
}

func JsonEncodeDecode() {
    m1 := Message2{3, 1024, "json"}
    var buf []byte
    var err error

    if buf, err = json.Marshal(m1); err != nil {
        log.Fatal("json marshal error:", err)
    }

    var m2 Message2
    if err = json.Unmarshal(buf, &m2); err != nil {
        log.Fatal("json unmarshal error:", err)
    }
}

四. Protobuf

当然,还可以使用 protobuf 来序列化.

test.proto

syntax = "proto2";
package example;

message Message {
    required uint64 id = 1;
    required uint64 size = 2;
    required string data = 3;
}
func ProtoEncodeDecode() {
    m1 := &example.Message{
        Id:   proto.Uint64(4),
        Size: proto.Uint64(1024),
        Data: proto.String("proto"),
    }

    buf, err := proto.Marshal(m1)
    if err != nil {
        log.Fatal("proto marshal error:", err)
    }

    var m2 example.Message
    if err = proto.Unmarshal(buf, &m2); err != nil {
        log.Fatal("proto unmarshal error:", err)
    }
    fmt.Println(m2.GetId(), m2.GetSize(), m2.GetData())
}

五. BenchMark 对比

现在来对比下这几种序列化方式的性能。

目录结构:

$ tree serialize
serialize
├── serialize.go
├── serialize_test.go
└── example
    ├── test.pb.go
    └── test.proto

serialize.go

package serialize

import (
    "bytes"
    "encoding/binary"
    "encoding/gob"
    "encoding/json"
    "log"
    "serialize/example"

    "github.com/golang/protobuf/proto"
)

type Message struct {
    Id   uint64
    Size uint64
}

type Message2 struct {
    Id   uint64 `json:"id"`
    Size uint64 `json:"size"`
    Data string `json:"data"`
}

func BinaryRW() {
    m1 := Message{1, 1024}
    buf := new(bytes.Buffer)

    if err := binary.Write(buf, binary.LittleEndian, m1); err != nil {
        log.Fatal("binary write error:", err)
    }

    var m2 Message
    if err := binary.Read(buf, binary.LittleEndian, &m2); err != nil {
        log.Fatal("binary read error:", err)
    }
}

func GobEncodeDecode() {
    m1 := Message2{2, 1024, "gob"}
    var buf bytes.Buffer

    enc := gob.NewEncoder(&buf)
    dec := gob.NewDecoder(&buf)

    if err := enc.Encode(m1); err != nil {
        log.Fatal("encode error:", err)
    }

    var m2 Message2
    if err := dec.Decode(&m2); err != nil {
        log.Fatal("decode error:", err)
    }
}

func JsonEncodeDecode() {
    m1 := Message2{3, 1024, "json"}
    var buf []byte
    var err error

    if buf, err = json.Marshal(m1); err != nil {
        log.Fatal("json marshal error:", err)
    }

    var m2 Message2
    if err = json.Unmarshal(buf, &m2); err != nil {
        log.Fatal("json unmarshal error:", err)
    }
}

func ProtoEncodeDecode() {
    m1 := &example.Message{
        Id:   proto.Uint64(4),
        Size: proto.Uint64(1024),
        Data: proto.String("proto"),
    }

    buf, err := proto.Marshal(m1)
    if err != nil {
        log.Fatal("proto marshal error:", err)
    }

    var m2 example.Message
    if err = proto.Unmarshal(buf, &m2); err != nil {
        log.Fatal("proto unmarshal error:", err)
    }
}

serialize_test.go

package serialize

import (
    "testing"
)

func BenchmarkBinaryRW(b *testing.B) {
    for i := 0; i < b.N; i++ {
        BinaryRW()
    }
}

func BenchmarkGobEncodeDecode(b *testing.B) {
    for i := 0; i < b.N; i++ {
        GobEncodeDecode()
    }
}

func BenchmarkJsonEncodeDecode(b *testing.B) {
    for i := 0; i < b.N; i++ {
        JsonEncodeDecode()
    }
}

func BenchmarkProtoEncodeDecode(b *testing.B) {
    for i := 0; i < b.N; i++ {
        ProtoEncodeDecode()
    }
}
BenchmarkBinaryRW-4              2000000           609 ns/op
BenchmarkGobEncodeDecode-4        100000         23689 ns/op
BenchmarkJsonEncodeDecode-4      1000000          1889 ns/op
BenchmarkProtoEncodeDecode-4     2000000           778 ns/op
PASS
ok      serialize   8.722s
方式 优点 缺点
binary 性能高 不支持不确定大小类型 int、slice、string
gob 支持多种类型 性能低
json 支持多种类型 性能低于 binary 和 protobuf
protobuf 支持多种类型,性能高 需要单独存放结构,如果结构变动需要重新生成 .pb.go 文件

写在最后

注意: 网络传输上面类似数据时,记得要考虑粘包问题。

你可能感兴趣的:(Golang)