Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

encoding/gob #682

Open
981377660LMT opened this issue Jan 1, 2025 · 1 comment
Open

encoding/gob #682

981377660LMT opened this issue Jan 1, 2025 · 1 comment

Comments

@981377660LMT
Copy link
Owner

No description provided.

@981377660LMT
Copy link
Owner Author

在 Go 语言中,encoding/gob 是一个用于在 Go 进程之间(或持久化到文件)传递自定义类型或数据结构的内置序列化/反序列化(编组/解组)机制。它的设计目标是:高效、类型安全、与 Go 紧密结合,可以看作是 Go 专属的二进制编码方案。

下面我们从几个方面来详细介绍 gob


1. gob 的特点

  1. 针对 Go 类型设计
    gob 的编码器会使用反射来分析 Go 语言的结构体、切片、映射等类型,将其打包成二进制流;解码时则根据流中的元信息自动构建对应类型的数据。

  2. 二进制高效
    相对于 JSON、XML 等文本格式,gob 使用二进制格式,序列化后体积更小、速度更快(通常也比 Protobuf 等更简单)。

  3. 支持自定义类型
    不需要再额外写“序列化/反序列化”方法,对于大多数常见的 Go 类型(结构体、数组、切片、map、指针等)都能自动处理。

  4. 必须在双方(发送端/接收端)使用相同或兼容的 Go 类型定义
    gob 并不适合跨语言(如 Python、Java)场景,因为它没有通用的跨语言规范。


2. 使用场景

  • Go 服务内部的进程间通信(IPC):通过 net.Connio.Writer,让一端使用 gob.Encoder 编码结构体,一端使用 gob.Decoder 解码。
  • 数据的持久化:可以把数据序列化为二进制形式存储到文件或数据库中,稍后再读出来反序列化。
  • 缓存:把某些结构体序列化后写到缓存中,下次读缓存再反序列化成原来的结构体。

3. 基本使用示例

3.1 序列化(Encode)

package main

import (
    "encoding/gob"
    "bytes"
    "fmt"
)

type MyStruct struct {
    Name  string
    Age   int
    Score float64
}

func main() {
    var buf bytes.Buffer
    enc := gob.NewEncoder(&buf)

    data := MyStruct{Name: "Alice", Age: 30, Score: 99.5}
    // 将 data 编码到 buf 中
    err := enc.Encode(data)
    if err != nil {
        panic(err)
    }

    // buf.Bytes() 就是序列化后的二进制数据
    fmt.Println("Encoded bytes length:", len(buf.Bytes()))
}

要点:

  1. 创建一个 bytes.Buffer 作为写入目标;
  2. gob.NewEncoder(io.Writer) 创建 Encoder
  3. 调用 Encoder.Encode(数据),将数据写入到 buf

3.2 反序列化(Decode)

func main() {
    var buf bytes.Buffer
    enc := gob.NewEncoder(&buf)

    data := MyStruct{Name: "Alice", Age: 30, Score: 99.5}
    err := enc.Encode(data)
    if err != nil {
        panic(err)
    }

    dec := gob.NewDecoder(&buf)
    var out MyStruct
    // 从 buf 中读取并解码到 out
    err = dec.Decode(&out)
    if err != nil {
        panic(err)
    }

    fmt.Printf("Decoded struct: %+v\n", out)
}

要点:

  1. 使用同一个 bytes.Buffer 或者从其他数据源(文件、网络连接等)拿到二进制流;
  2. 调用 gob.NewDecoder(io.Reader) 创建 Decoder
  3. 调用 Decoder.Decode(&变量),将二进制流解码到指定变量中。

4. 需要注意的点

  1. 在网络通信场景中,需要先“注册(Register)”自定义类型吗?

    • 一般情况下不需要手动 gob.Register(...),因为编码器在 Encode 时就会把类型信息写入到流中,解码端能自动获取类型信息并构造相同的类型。
    • 只有当你需要传递的是 interface{} 类型的值,或者结构体里嵌套了复杂多态结构时,才可能需要 gob.Register(...) 让解码器知道真正的底层类型。
  2. 版本兼容性

    • 如果发送端和接收端的结构体字段不完全一样(多了一些字段或少了一些字段),有时依旧可以解码成功,只要基础字段类型能对上。但如果差异过大或字段类型不兼容,就会解码失败。
    • 建议在同一版本或兼容版本的 Go 类型之间使用 gob
  3. 循环引用

    • gob 能处理一些复杂场景下的循环引用,但可能需要消耗额外的元数据空间;如果结构过于复杂,序列化效率也会下降。
  4. 性能比较

    • 相比 JSON:gob 序列化后体积更小、速度更快,但可读性更差(是二进制)。
    • 相比 Protobuf/FlatBuffers/Thrift:gob 使用更灵活,对 Go 类型支持好,但不适合跨语言且没有严格的“schema”定义;后者有更广泛的跨语言生态和明确的接口定义。

5. 一个网络通信示例

下面演示如何在服务器和客户端间使用 gob 传递自定义对象。假设我们开一个简单的 TCP 服务器,等待连接,然后发送一个结构体给客户端:

5.1 服务器端

package main

import (
    "encoding/gob"
    "fmt"
    "net"
)

type Person struct {
    Name string
    Age  int
}

func main() {
    ln, err := net.Listen("tcp", ":8080")
    if err != nil {
        panic(err)
    }
    fmt.Println("Server listening on :8080")

    for {
        conn, err := ln.Accept()
        if err != nil {
            fmt.Println("Accept error:", err)
            continue
        }

        go handleConn(conn)
    }
}

func handleConn(conn net.Conn) {
    defer conn.Close()

    enc := gob.NewEncoder(conn)
    person := Person{Name: "Bob", Age: 42}

    err := enc.Encode(person)
    if err != nil {
        fmt.Println("Encode error:", err)
        return
    }
    fmt.Println("Sent Person:", person)
}

5.2 客户端

package main

import (
    "encoding/gob"
    "fmt"
    "net"
)

type Person struct {
    Name string
    Age  int
}

func main() {
    conn, err := net.Dial("tcp", ":8080")
    if err != nil {
        panic(err)
    }
    defer conn.Close()

    dec := gob.NewDecoder(conn)
    var p Person
    err = dec.Decode(&p)
    if err != nil {
        panic(err)
    }
    fmt.Println("Received Person:", p)
}
  • 服务器接受连接后,用 gob.NewEncoder(conn) 将一个 Person 结构体编码到网络流中;
  • 客户端连上后,通过 gob.NewDecoder(conn) 解码并获取到 Person

6. 常见问题

  1. 为什么要用 interface{} 或者指针时,需要 gob.Register()

    • 当序列化的数据是 interface{}gob 并不知道它实际包含的具体类型。为了让解码器了解类型信息,需要在编码前明确告诉 gob:这个 “实现 interface{} 的具体类型” 是什么。
    • 类似地,如果一个字段是 *StructA 或者带有多态性时,也可能需要显式注册。
  2. Gob 是否可以跨语言使用?

    • 一般不行,或者需要自行实现其他语言的 gob 解析器,代价较大;
    • gob 序列化格式对 Go 语言特性和类型系统耦合度很高,官方也主要定位在 Go 内部通信。
  3. 需要手动关闭 Encoder/Decoder 吗?

    • 通常无需手动关闭;它们依赖的只是底层 io.Writerio.Reader。只要确保相关的网络连接或文件已经关闭/flush,Encoder/Decoder 不再使用即可。
  4. 序列化后数据会出现多余的元信息,可能造成重复

    • gob 为每种类型分配了一个“类型 ID”,第一次出现时会输出类型描述,后续就只引用这个 ID,从而避免重复输出描述信息。不过,如果多次编码多个不同类型的对象,就会包含多个类型描述。
    • 在绝大多数场景下,这种元信息的开销是可以接受的,比文本格式依旧要小很多。

7. 总结

  • gob 是 Go 语言原生提供的序列化机制,优势在于与 Go 类型系统结合紧密编码解码性能好使用方式简单
  • 适用于 Go 内部 或者 同版本兼容 的场景,比如进程间通信、内存结构的持久化或缓存。
  • 不适合跨语言场景,或者需要更灵活/通用性更强的接口协议(Protobuf/JSON 等)。

如果你的需求是:

  • 在 Go 内部
  • 有丰富的结构体类型,不想写额外的序列化代码;
  • 对性能和序列化体积有要求
  • 不需要跨语言兼容
    那么使用 gob 通常会是一个非常理想的选择。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant