Go 標準庫 encoding/gob 快速上手

文章目錄

  • 1.簡介
  • 2.基礎
  • 3.類型和值
  • 4.編碼細節
  • 5.安全
  • 6.主要函數
    • 6.1 注冊
      • 1. 接口的底層類型在運行時才能確定
      • 2.類型標識的唯一性
      • 3.安全性與顯式意圖
      • 4.與結構體的自動處理對比
      • 5.示例分析
      • 為什么不能像 JSON 那樣自動處理?
      • 總結
    • 6.2 編碼
    • 6.3 解碼
  • 7.示例
    • 7.1 編解碼結構體
    • 7.2 編解碼接口
    • 7.3 文件讀寫
    • 7.4 自定義編解碼方式
  • 8.優勢與局限
    • 8.1 優勢
    • 8.2 局限
    • 8.3 小結
  • 9.最佳實踐
    • 類型兼容性
    • 注冊策略
    • 安全考慮
    • 調試技巧
    • 替代方案考慮
  • 參考文獻

1.簡介

encoding/gob 是 Go 語言標準庫中一個用于 Go 數據結構與二進制流之間序列化和反序列化的制協議包。

gob 包用來管理 gob 流,它可以實現在編碼器(發送器)和解碼器(接收器)之間進行二進制數據流的發送,一般用來傳遞遠端程序調用的參數和結果,比如 net/rpc 包就有用到這個。

gob 全稱 GOlang Binary。go 代表 Go 語言,binary 表示其使用二進制編碼(而非 JSON/XML 等文本格式)。

2.基礎

gob 流具有自描述性。流中的每個數據項前面都有一個前綴(采用一個預定義類型的集合)指明其類型。指針不會被傳輸,但它們指向的內容會被傳輸;也就是說,值會被展平。不允許使用零指針,因為它們沒有值。遞歸類型可以正常工作,但遞歸值(帶循環的數據)存在問題。這種情況可能會有所改變。

要使用 gob,需要先創建一個編碼器,并向其提供一系列數據項,這些數據項可以是值,也可以是可以解引用到值的地址。編碼器會確保所有類型信息在需要之前都已發送。在接收端,解碼器會從已編碼的數據流中檢索值,并將其解包到局部變量中。

3.類型和值

源和目標的值/類型不必完全對應。

對于結構體,如果源中有字段(通過名稱標識),但接收變量中沒有,則將被忽略。如果接收變量中有字段,但傳輸類型或值中沒有,則目標變量中也會忽略這些字段。如果兩個變量中都存在同名字段,則它們的類型必須兼容。接收方和發送方都會執行所有必要的間接和解引用操作,以便在 gob 和實際的 Go 值之間進行轉換。

例如,一個 gob 類型如下:

struct { A, B int }

可以從以下任意 Go 類型發送或接收:

struct { A, B int } // 相同
*struct { A, B int } // 結構的額外間接
struct { *A, **B int } // 字段的額外間接
struct { A, B int64 } // 不同的具體值類型;見下文

它也可以被接收進以下任何一個:

struct { A, B int } 	// 相同
struct { B, A int } 	// 順序不重要;按名稱匹配
struct { A, B, C int } 	// 忽略額外字段 (C)
struct { B int } 		// 忽略缺失字段(A);數據從數據流中將被刪除
struct { B, C int } 	// 忽略缺失字段(A);忽略額外字段(C)。

嘗試接收下面這些類型將引發解碼錯誤:

struct { A int; B uint } 	// 改變 B 的符號
struct { A int; B float } 	// 改變 B 的類型
struct { } 					// 沒有共同的字段名稱
struct { C, D int } 		// 沒有共同的字段名稱

整數的傳輸方式有兩種:任意精度有符號整數或任意精度無符號整數。

gob 格式不區分 int8、int16 等整數類型;只區分有符號整數和無符號整數。如下所述,發送方以變長編碼發送值;接收方接收該值并將其存儲在目標變量中。浮點數始終使用 IEEE 754 64 位精度發送。

有符號整數可以被任何有符號整數變量接收:int、int16 等;無符號整數可以被任何無符號整數變量接收;浮點值可以被任何浮點變量接收。但是,目標變量必須能夠表示該值,否則解碼操作將失敗。

結構體、數組和切片也受支持。結構體僅對導出的字段進行編碼和解碼。字符串和字節數組支持一種特殊且高效的表示形式(見下文)。切片解碼時,如果現有切片具有容量,則切片將進行擴展;如果容量不足,則分配一個新數組。無論如何,結果切片的長度都會包含解碼后的元素數量。

通常,如果需要分配內存,解碼器會分配內存。如果不需要,它會使用從流中讀取的值來更新目標變量。解碼器不會先初始化目標變量,因此如果目標是復合值(如 map、struct 或 slice),解碼后的值將按元素合并到現有變量中。

函數和通道不會通過 gob 發送。嘗試在頂層編碼此類值將會失敗。chan 或 func 類型的結構體字段將被視為未導出的字段,并被忽略。

4.編碼細節

本節記錄了編碼細節,這些細節對大多數用戶來說并不重要(可以跳過)。詳細信息按自下而上的方式呈現。

無符號整數的發送方式有兩種。

如果小于 128,則以包含該值的字節形式發送。否則,將以最小長度的大端字節序(高字節優先)字節流的形式發送,該字節流包含該值,并在其前面附加一個字節,該字節包含字節數(取反)。因此,0 的發送方式為 (00),7 的發送方式為 (07),256 的發送方式為 (FE 01 00)。

布爾值在無符號整數內進行編碼:0 表示假,1 表示真。

有符號整數 i 被編碼在無符號整數 u 中。u 中的位 1 及以上表示其值;位 0 表示在接收時是否需要對其進行補碼。編碼算法如下:

var u uint
if i < 0 {u = (^uint(i) << 1) | 1 // complement i, bit 0 is 1
} else {u = (uint(i) << 1) // do not complement i, bit 0 is 0
}
encodeUnsigned(u)

因此,低位類似于符號位,但將其設為補碼位可以保證最大負整數不是特例。例如,-129=^128=(^256>>1)編碼為 (FE 01 01)。

浮點數始終以 float64 值的表示形式發送。該值使用math.Float64bits轉換為 uint64 。然后,uint64 會被字節反轉,并作為常規無符號整數發送。字節反轉意味著尾數的指數和高精度部分先發送。由于低位通常為零,這可以節省編碼字節數。例如,17.0 僅用三個字節編碼 (FE 31 40)。

字符串和字節切片以無符號計數的形式發送,后跟該值的許多未解釋的字節。

所有其他切片和數組都以無符號計數的形式發送,后跟使用標準 gob 編碼遞歸發送的多個元素。

映射以無符號計數的形式發送,后跟對應數量的鍵值對和元素對。發送的是空但非零的映射,因此如果接收方尚未分配映射,則在接收時始終會分配一個,除非發送的映射為零且不在頂層。

在切片和數組以及映射中,所有元素,甚至是零值元素,都會被傳輸,即使所有元素都是零。

結構體以 (字段編號,字段值) 對的序列形式發送。字段值使用其類型的標準 gob 編碼遞歸發送。如果某個字段的值為其類型的零值(數組除外;參見上文),則該字段將在傳輸中被省略。字段編號由編碼結構體的類型定義:編碼類型的第一個字段為字段 0,第二個字段為字段 1,依此類推。在對值進行編碼時,為了提高效率,字段編號會進行增量編碼,并且字段始終按字段編號遞增的順序發送;因此增量是無符號的。增量編碼的初始化將字段編號設置為 -1,因此值為 7 的無符號整數字段 0 將被傳輸為無符號增量 = 1、無符號值 = 7 或 (01 07)。最后,在所有字段都發送完畢后,一個終止標記表示結構體的結束。該標記是一個增量 = 0 的值,其表示形式為 (00)。

接口類型不進行兼容性檢查;所有接口類型在傳輸時都被視為單個“接口”類型的成員,類似于 int 或 []byte ——實際上它們都被視為 interface{}。接口值以字符串形式傳輸,該字符串標識要發送的具體類型(該名稱必須通過調用 Register 預定義),后跟表示后續數據長度的字節數(因此,如果無法存儲該值,則可以跳過該值),然后是對存儲在接口值中的具體(動態)值的常規編碼。(nil 接口值由空字符串標識,不傳輸任何值。)解碼器接收后,會驗證解包后的具體項是否滿足接收變量的接口要求。

如果將一個值傳遞給 Encoder.Encode,并且其類型不是結構體(或指向結構體的指針等),為了簡化處理,它會被表示為一個包含一個字段的結構體。這樣做唯一可見的效果是在值之后編碼一個零字節,就像在已編碼結構體的最后一個字段之后一樣,這樣解碼算法就能知道頂級值何時完成。

類型的表示如下所述。當在編碼器 (Encoder) 和解碼器 (Decoder) 之間的給定連接上定義類型時,它會被分配一個有符號整數類型 ID。當調用 Encoder.Encode(v) 時,它會確保為 v 的類型及其所有元素分配一個 ID,然后發送 (typeid, encoding-v) 對,其中 typeid 是 v 編碼類型的類型 ID,encoded-v 是值 v 的 gob 編碼。

為了定義類型,編碼器選擇一個未使用的正類型 ID,并發送對 (-type id, encoding-type),其中 encoding-type 是 wireType 描述的 gob 編碼,由以下類型構成:

type wireType struct {ArrayT           *arrayTypeSliceT           *sliceTypeStructT          *structTypeMapT             *mapTypeGobEncoderT      *gobEncoderTypeBinaryMarshalerT *gobEncoderTypeTextMarshalerT   *gobEncoderType
}
type arrayType struct {CommonTypeElem typeIdLen  int
}
type CommonType struct {Name string // the name of the struct typeId  int    // the id of the type, repeated so it's inside the type
}
type sliceType struct {CommonTypeElem typeId
}
type structType struct {CommonTypeField []fieldType // the fields of the struct.
}
type fieldType struct {Name string // the name of the field.Id   int    // the type id of the field, which must be already defined
}
type mapType struct {CommonTypeKey  typeIdElem typeId
}
type gobEncoderType struct {CommonType
}

如果有嵌套類型 ID,則在使用頂級類型 ID 描述編碼 v 之前,必須定義所有內部類型 ID 的類型。

為了簡化設置,連接被定義為先驗地理解這些類型,以及基本 gob 類型 int、uint 等。它們的 ID 是:

bool        1
int         2
uint        3
float       4
[]byte      5
string      6
complex     7
interface   8
// gap for reserved ids.
WireType    16
ArrayType   17
CommonType  18
SliceType   19
StructType  20
FieldType   21
// 22 is slice of fieldType.
MapType     23

最后,通過調用 Encode 創建的每條消息前面都會有一個編碼的無符號整數計數,表示消息中剩余的字節數。在初始類型名稱之后,接口值也以相同的方式包裝;實際上,接口值的行為就像是 Encode 的遞歸調用。

總結一下,gob 流看起來像這樣:

(byteCount (-type id, encoding of a wireType)* (type id, encoding of a value))*

其中 * 表示零次或多次重復,并且值的類型 ID 必須預先定義或在流中的值之前定義。

兼容性:此軟件包的任何未來更改都將盡力保持與使用先前版本編碼的流的兼容。也就是說,此軟件包的任何發布版本都應該能夠解碼使用任何先前發布版本寫入的數據,但可能會受到安全修復等問題的制約。有關背景信息,請參閱 Go 兼容性文檔:https://golang.org/doc/go1compat。

有關 gob wire 格式的設計討論,請參閱 “Gobs of data”:https://blog.golang.org/gobs-of-data。

5.安全

此軟件包并非針對對抗性輸入進行加固,且不在 https://go.dev/security/policy 的范圍內。具體而言,解碼器僅對解碼后的輸入大小進行基本的完整性檢查,且其限制不可配置。解碼來自不受信任來源的 gob 數據時應格外小心,因為這可能會消耗大量資源。

6.主要函數

6.1 注冊

Register 和 RegisterName 函數都用于注冊具體類型以實現接口的編解碼。

  • func Register
// Register 記錄 value 具體值對應的類型和其名稱。
// 該名稱將用來識別發送或接受接口類型值時下層的具體類型。
// 本函數只應在初始化時調用,如果類型和名字的映射不是一一對應的,會 panic。
func Register(value any)
  • func RegisterName
// RegisterName 與 Register 類似,但使用提供的名稱而不是類型的默認名稱。
func RegisterName(name string, value any)

func Register 底層會調用 func RegisterName。

為什么編解碼接口要提前注冊具體類型呢?

主要原因涉及類型安全、編解碼機制和運行時動態處理的需求。以下是詳細解釋:

1. 接口的底層類型在運行時才能確定

  • 接口變量在編譯時只有抽象類型信息(如 Animal),但實際存儲的是具體類型(如 DogCat)。
  • Gob 在編碼時需要知道接口背后的具體類型,才能正確序列化數據;解碼時需要通過注冊信息還原出正確的具體類型。
  • 如果不注冊:解碼器無法知道應該將數據還原為 Dog 還是 Cat,導致解碼失敗。

2.類型標識的唯一性

  • Gob 通過注冊機制為每個具體類型分配唯一的內部標識符(如 main.Dog)。
  • 編碼時,Gob 會寫入這個標識符;解碼時,通過標識符查找已注冊的類型,并調用對應的解碼方法。
  • 如果不注冊:解碼器無法將接收到的數據映射到正確的 Go 類型。

3.安全性與顯式意圖

  • 強制注冊是一種安全機制,防止意外解碼未預期的類型(類似反序列化攻擊)。
  • 開發者必須顯式聲明“允許通過接口傳輸哪些具體類型”,避免運行時不可控行為。

4.與結構體的自動處理對比

  • 結構體:如果編解碼雙方有相同的類型定義,Gob 可以自動推導類型信息,因為結構體名稱和字段是確定的。
  • 接口:具體類型是動態的,無法通過靜態分析確定,必須依賴運行時的注冊信息。

5.示例分析

type Animal interface { Speak() string }
type Dog struct { Name string }
type Cat struct { Name string }func init() {gob.Register(Dog{}) // 必須注冊gob.Register(Cat{}) // 必須注冊
}func send(a Animal) {// 編碼時,Gob 需要知道 a 的具體類型是 Dog 還是 Cat// 通過注冊表,可以找到 Dog 或 Cat 的類型標識符
}

為什么不能像 JSON 那樣自動處理?

  • JSON 等文本協議通過字段名(如 "type": "dog")顯式標識類型,但 Gob 是二進制協議,設計上追求高效和緊湊,不存儲冗余的類型描述。
  • Gob 的注冊機制避免了每次傳輸都附帶完整的類型信息,提升了性能。

總結

Gob 要求注冊接口的具體類型,本質上是為了解決接口的動態類型特性與二進制編解碼的靜態需求之間的矛盾。這是一種在靈活性、安全性和性能之間的權衡設計。

6.2 編碼

數據在傳輸時會先經過編碼(序列化)后再進行傳輸,與編碼相關的有三個方法:

  • func NewEncoder
// NewEncoder 返回一個將在 io.Writer 上傳輸的新編碼器。
func NewEncoder(w io.Writer) *Encoder
  • func (*Encoder) Encode
// Encode 會傳輸接口值所表示的數據項,并保證所有必要的類型信息都已傳輸完畢。
// 向 Encoder 傳遞一個 nil 指針會導致 panic,因為 gob 無法傳輸此類數據。
func (enc *Encoder) Encode(e any) error
  • func (*Encoder) EncodeValue
// EncodeValue 傳輸反射值所表示的數據項,并保證所有必要的類型信息都已傳輸完畢。
// 將 nil 指針傳遞給 EncodeValue 會導致 panic,因為它們無法通過 gob 傳輸。
func (enc *Encoder) EncodeValue(value reflect.Value) error

6.3 解碼

接收到數據后需要對數據進行解碼(序列化),與解碼相關的有三個方法:

  • func NewDecoder
// NewDecoder 返回一個從 io.Reader 讀取數據的新解碼器。
// 如果 r 未實現 io.ByteReader 接口,則會將其包裝在 bufio.Reader 中。
func NewDecoder(r io.Reader) *Decoder
  • func (*Decoder) Decode
// Decode 從輸入流中讀取下一個值,并將其存儲在空接口值所表示的數據中。
// 如果 e 為 nil,則該值將被丟棄。否則,e 的底層值必須是指向下一個接收數據項的正確類型的指針。
// 如果輸入位于 EOF,解碼將返回 io.EOF 并且不修改 e。
func (dec *Decoder) Decode(e any) error
  • func (*Decoder) DecodeValue
// DecodeValue 從輸入流中讀取下一個值。
// 如果 v 為零的 reflect.Value(v.Kind() == Invalid),DecodeValue 會丟棄該值。否則,它會將值存儲到 v 中。在這種情況下,v 必須表示一個指向數據的非零指針,或者是一個可賦值的 reflect.Value(v.CanSet())。
// 如果輸入位于 EOF,DecodeValue 會返回 io.EOF 并且不會修改 v。
func (dec *Decoder) DecodeValue(v reflect.Value) error

7.示例

7.1 編解碼結構體

package mainimport ("bytes""encoding/gob""fmt""log"
)type Person struct {Name stringAge  int
}func main() {// 創建數據alice := Person{Name: "Alice", Age: 30}// 序列化var buf bytes.Bufferencoder := gob.NewEncoder(&buf)if err := encoder.Encode(alice); err != nil {log.Fatal("Encode error:", err)}fmt.Printf("Serialized data: %x\n", buf.Bytes())// 反序列化var bob Persondecoder := gob.NewDecoder(&buf)if err := decoder.Decode(&bob); err != nil {log.Fatal("Decode error:", err)}fmt.Printf("Deserialized: %+v\n", bob)
}

運行輸出:

Serialized data: 247f03010106506572736f6e01ff8000010201044e616d65010c00010341676501040000000cff800105416c696365013c00
Deserialized: {Name:Alice Age:30}

7.2 編解碼接口

package mainimport ("bytes""encoding/gob""fmt""log"
)type Animal interface {Sound() string
}type Dog struct{ Name string }func (d Dog) Sound() string { return "Woof!" }type Cat struct{ Name string }func (c Cat) Sound() string { return "Meow!" }func interfaceExample() {// 注冊具體類型gob.Register(Dog{})gob.Register(Cat{})animals := []Animal{Dog{Name: "Rex"},Cat{Name: "Whiskers"},}// 序列化var buf bytes.Bufferif err := gob.NewEncoder(&buf).Encode(animals); err != nil {log.Fatal(err)}// 反序列化var decoded []Animalif err := gob.NewDecoder(&buf).Decode(&decoded); err != nil {log.Fatal(err)}for _, a := range decoded {fmt.Printf("%T: %s says %s\n", a, a.(interface{ GetName() string }).GetName(), a.Sound())}
}// 為類型添加GetName方法以便類型斷言
func (d Dog) GetName() string { return d.Name }
func (c Cat) GetName() string { return c.Name }func main() {interfaceExample()
}

運行輸出:

main.Dog: Rex says Woof!
main.Cat: Whiskers says Meow!

注意,編解碼接口時需要提前注冊具體類型,否則會報如下錯誤:

gob: type not registered for interface: main.Dog

7.3 文件讀寫

也可以使用 gob 將序列化后的數據持久化到磁盤文件。

func fileStorage() {type Config struct {APIKey stringPort   int}cfg := Config{APIKey: "secret123", Port: 8080}// 寫入文件file, err := os.Create("config.gob")if err != nil {log.Fatal(err)}defer file.Close()if err := gob.NewEncoder(file).Encode(cfg); err != nil {log.Fatal(err)}// 從文件讀取file, err = os.Open("config.gob")if err != nil {log.Fatal(err)}defer file.Close()var loaded Configif err := gob.NewDecoder(file).Decode(&loaded); err != nil {log.Fatal(err)}fmt.Printf("Loaded config: %+v\n", loaded)
}

運行輸出:

Loaded config: {APIKey:secret123 Port:8080}

7.4 自定義編解碼方式

Gob 可以通過調用相應的方法(按優先順序)對實現了 GobEncoder 或 encoding.BinaryMarshaler 接口的任何類型的值進行編碼。

Gob 可以通過調用相應的方法(按優先順序)對實現了 GobDecoder 或 encoding.BinaryUnmarshaler 接口的任何類型的值進行解碼。

package mainimport ("bytes""encoding/gob""fmt""log"
)// Vector 類型實現了BinaryMarshal/BinaryUnmarshal 方法,這樣我們就可以發送和接受 gob 類型的數據。
type Vector struct {x, y, z int
}func (v Vector) MarshalBinary() ([]byte, error) {// A simple encoding: plain text.var b bytes.Buffer_, _ = fmt.Fprintln(&b, v.x, v.y, v.z)return b.Bytes(), nil
}// UnmarshalBinary 修改接收器,所以必須要傳遞指針類型
func (v *Vector) UnmarshalBinary(data []byte) error {// A simple encoding: plain text.b := bytes.NewBuffer(data)_, err := fmt.Fscanln(b, &v.x, &v.y, &v.z)return err
}// 此示例傳輸實現自定義編碼和解碼方法的值。
func main() {var network bytes.Buffer// 創建一個編碼器發送數據enc := gob.NewEncoder(&network)err := enc.Encode(Vector{3, 4, 5})if err != nil {log.Fatal("encode:", err)}// 創建一個解碼器接收數據dec := gob.NewDecoder(&network)var v Vectorerr = dec.Decode(&v)if err != nil {log.Fatal("decode:", err)}fmt.Printf("%#v\n", v)
}

運行輸出:

main.Vector{x:3, y:4, z:5}

8.優勢與局限

8.1 優勢

  • Go 原生高性能

gob 使用二進制格式進行編解碼,性能比 JSON/XML 快 2-5 倍,數據體積小 30-70%。

  • 零配置自動化

自動處理復雜類型:

type Complex struct {Slice  []*intMap    map[string]chan struct{}Func   func() // 不支持!
}
// 自動支持:切片、指針、映射、結構體嵌套
  • 版本演進支持

對結構體新增、刪除字段或順序調整有較好的兼容性。

// V1 結構
type User struct { ID int; Name string }// V2 結構(添加字段)
type User struct { ID int; Name string; Email string }// V1 數據 → V2 解碼:Email 自動置零值// 舊版本
type Config struct { Host string; Port int }// 新版本(字段調序)仍可兼容
type Config struct { Port int; Host string }
  • 循環引用處理
type Node struct {Value intNext  *Node // 循環指針
}n1 := &Node{Value: 1}
n2 := &Node{Value: 2, Next: n1}
n1.Next = n2 // 循環引用// Gob 完美序列化/反序列化

8.2 局限

  • 跨語言不兼容

gob 是 Golang 自有的二進制編解碼方案,與其他語言不兼容。

  • 接口類型約束

編解碼接口類型時,必須預注冊。

type Encoder interface { Encode() []byte }func main() {var enc Encoder = MyEncoder{}gob.Register(MyEncoder{}) // 必須!gob.Encode(enc)
}
  • 結構演進限制

破壞性變更不可逆。

變更類型是否兼容后果
添加字段?新字段為零值
刪除字段?忽略不存在的字段,正常解碼
重命名字段?數據丟失
修改字段類型?解碼崩潰
  • 安全風險

反序列化漏洞。

// 不可信來源的gob數據可能:
// 1. 導致內存耗盡(大容器攻擊)
// 2. 觸發未預期類型重建
// 3. 暴露私有字段(通過反射)
  • 性能邊界

不適合性能要求的極端場景。

場景Gob 性能替代方案
100K+ QPS?? 中等FlatBuffers
微秒級延遲要求? 不足Cap’n Proto
移動設備?? 較重MessagePack

8.3 小結

優勢與局限對比表:

特性優勢局限性
語言支持Go原生深度優化僅限Go,無跨語言能力
開發效率零配置/自動類型處理接口需手動注冊
性能比文本協議快5倍仍慢于FlatBuffers等零拷貝方案
數據兼容支持向前擴展字段刪除/重命名字段破壞兼容性
類型系統完美支持Go復雜類型不支持func、chan等類型
安全無遠程代碼執行風險仍可能遭受資源耗盡攻擊
調試便捷性數據不可讀(需專用工具)JSON更易調試

9.最佳實踐

類型兼容性

  • 添加新字段到結構體末尾以保持向后兼容。
  • 不要刪除或重命名字段。
// 兼容性示例
type V1 struct { A int }
type V2 struct { A int B string // 新增字段在末尾
}

注冊策略

  • 在 init() 函數中注冊類型。

  • 跨服務使用 RegisterName 保持名稱一致。

安全考慮

  • 不要反序列化不可信來源的數據。
  • 對于網絡傳輸,添加加密/認證層。

調試技巧

// 調試編碼數據
fmt.Printf("%x\n", buf.Bytes())// 或者轉換為字符串查看(可能包含可讀內容)
fmt.Println(buf.String())

替代方案考慮

  • 需要跨語言:使用 JSON 或 Protocol Buffers。
  • 需要人類可讀:使用 JSON。
  • 極致性能:考慮 MessagePack 或 FlatBuffers。

通過這份快速指南,您應該能夠立即開始使用 encoding/gob 進行高效的數據序列化操作。對于大多數 Go 服務間的通信需求,gob 提供了簡單高效的解決方案。


參考文獻

gob package - encoding/gob

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/86325.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/86325.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/86325.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Ubuntu ifconfig 查不到ens33網卡

BUG&#xff1a;ifconfig查看網絡配置信息&#xff1a; 終端輸入以下命令&#xff1a; sudo service network-manager stop sudo rm /var/lib/NetworkManager/NetworkManager.state sudo service network-manager start - service network - manager stop &#xff1a;停止…

算法-數論

C-小紅的數組查詢&#xff08;二&#xff09;_牛客周賽 Round 95 思路&#xff1a;不難看出a數組是有循環的 d3,p4時&#xff0c;a數組&#xff1a;1、0、3、2、1、0、3、2....... 最小循環節為4&#xff0c;即最多4種不同的數 d4,p6時&#xff0c;a數組&#xff1a;1、5、3、…

CSS中text-align: justify文本兩端對齊

text-align: justify; 是 CSS 中用于控制文本對齊方式的屬性值&#xff0c;它的核心作用是讓文本兩端對齊&#xff08;分散對齊&#xff09;&#xff0c;使段落左右邊緣整齊排列。以下是詳細解析&#xff1a; 作用效果 均勻分布間距 瀏覽器會自動調整單詞/字符之間的間距&#…

WebFuture:啟動數據庫提示: error while loading shared libraries: libaio.so.1問題處理

問題分析 當出現./mysqld: error while loading shared libraries: libaio.so.1: cannot open shared object file: No such file or directory這個錯誤時&#xff0c;這意味著 MySQL 服務器&#xff08;mysqld&#xff09;在啟動過程中無法找到libaio.so.1這個共享庫文件。li…

74常用控件_QSpacerItem的使用

目錄 代碼?例: 創建?組左右排列的按鈕. Spacer 使?布局管理器的時候, 可能需要在控件之間, 添加?段空?. 就可以使? QSpacerItem 來表?. 核?屬性 屬性說明width寬度height高度hData水平方向的 sizePolicy - QSizePolicy::Ignored&#xff1a;忽略控件的尺寸&#xf…

vmware 設置 dns

vmware 設置 dns 常用的 DNS&#xff08;Domain Name System&#xff09;服務器地址可以幫助你更快、更安全地解析域名。以下是一些國內外常用的公共 DNS 服務&#xff1a; 國內常用 DNS 阿里云 DNS IPv4: 223.5.5.5、223.6.6.6IPv6: 2400:3200::1、2400:3200:baba::1特點&am…

從一次日期格式踩坑經歷,談談接口設計中的“約定大于配置“

從一次日期格式踩坑經歷&#xff0c;談談接口設計中的"約定大于配置" 背景 最近在對接一個第三方接口時&#xff0c;遇到了一個有趣的"坑"。接口文檔中要求傳入一個符合 RFC3339 格式的日期時間字符串&#xff0c;格式示例為&#xff1a;2019-10-01T08:1…

高考數學易錯考點01 | 臨陣磨槍

文章目錄 前言集合與函數不等式數列三角函數 前言 本篇內容下載于網絡&#xff0c;網絡上的都是以 WORD 版本呈現&#xff0c;缺字缺圖很不完整&#xff0c;沒法使用&#xff0c;我只是做了補充和完善。有空準備進行第二次完善&#xff0c;添加問題解釋的鏈接。 集合與函數 …

YOLO12 改進|融入 Mamba 架構:插入視覺狀態空間模塊 VSS Block 的硬核升級

在醫學圖像分割領域&#xff0c;傳統卷積神經網絡&#xff08;CNNs&#xff09;受限于局部感受野&#xff0c;難以捕捉長距離依賴關系&#xff0c;而基于 Transformer 的模型因自注意力機制的二次計算復雜度&#xff0c;在處理高分辨率圖像時效率低下。近年來&#xff0c;狀態空…

MATLAB遍歷生成20到1000個節點的無線通信網絡拓撲推理數據

功能&#xff1a; 遍歷生成20到1000個節點的無線通信網絡拓撲推理數據&#xff0c;包括網絡拓撲和每個節點發射的電磁信號&#xff0c;采樣率1MHz/3000&#xff0c;信號時長5.7s&#xff0c;單幀數據波形為實采 數據生成效果&#xff1a; 拓撲及空間位置&#xff1a; 節點電磁…

oss:上傳圖片到阿里云403 Forbidden

訪問圖片出現403Forbidden問題&#xff0c;我們可以直接登錄oss賬號&#xff0c;查看對應權限是否開通&#xff0c;是否存在跨域問題

香橙派3B學習筆記8:snap安裝管理軟件包_打包倆個有調用的python文件

現在嘗試一下打包多個有互相調用的 py程序&#xff1a; ssh &#xff1a; orangepi本地ip 密碼 &#xff1a; orangepi 操作系統發行版&#xff1a; 基于 Ubuntu 20.04.6 LTS&#xff08;Focal Fossa&#xff09;的定制版本&#xff0c;專門為 Orange Pi 設備優化。PRETTY_NAM…

Spring Boot 中實現 HTTPS 加密通信及常見問題排查指南

Spring Boot 中實現 HTTPS 加密通信及常見問題排查指南 在金融行業安全審計中&#xff0c;未啟用HTTPS的Web應用被列為高危漏洞。通過正確配置HTTPS&#xff0c;可將中間人攻擊風險降低98%——本文將全面解析Spring Boot中HTTPS的實現方案與實戰避坑指南。 一、HTTPS 核心原理與…

前端對WebSocket進行封裝,并建立心跳監測

WebSocket的介紹&#xff1a; WebSocket 是一種在客戶端和服務器之間進行全雙工、雙向通信的協議。它是基于 HTTP 協議&#xff0c;但通過升級&#xff08;HTTP 升級請求&#xff09;將連接轉換為 WebSocket 協議&#xff0c;從而提供更高效的實時數據交換。 WebSocket 的特點…

【AI】智駕地圖在不同自動駕駛等級中的作用演變

一、功能價值動態模型&#xff1a;基于自動駕駛等級的權重遷移 功能演變四階段&#xff1a; █ 輔助階段&#xff08;L2&#xff09;&#xff1a;單功能補足 → █ 拓展階段&#xff08;L2 NOA&#xff09;&#xff1a;多模態增強 → █ 融合階段&#xff08;L3&#xff09;…

Java處理字符數組轉換為開始日期和結束日期

在Java中處理字符數組表示的TransactionTime&#xff08;例如["2025-06-01","2025-06-10"]&#xff09;&#xff0c;將其轉換為開始時間和結束時間&#xff0c;推薦使用Java 8的java.time API&#xff08;如LocalDate&#xff09;。以下是完整代碼示例&…

【筆記】Poetry虛擬環境創建示例

#工作記錄 【筆記】結合 Conda任意創建和配置不同 Python 版本的雙軌隔離的 Poetry 虛擬環境-CSDN博客 在PowerShell中&#xff1a; Windows PowerShell Copyright (C) Microsoft Corporation. All rights reserved.Install the latest PowerShell for new features and improv…

20242817李臻-安全文件傳輸系統-項目驗收

安全文件傳輸系統項目報告 項目概述 本實驗旨在設計并實現一個完整的安全文件管理系統&#xff0c;基于SM2SM3SM4混合密碼體系&#xff0c;構建了一個具備高安全性的C/S架構文件傳輸平臺。項目采用C/S架構&#xff0c;使用Qt框架開發&#xff0c;滿足Linux系統調用、Socket網…

2025年- H76-Lc184--55.跳躍游戲(貪心)--Java版

1.題目描述 2.思路 只要是在最大覆蓋范圍覆蓋了&#xff0c;就是覆蓋了。 局部最優&#xff1a;每遍歷一個元素取它最大的覆蓋范圍 全局最優&#xff1a;在這個序列里&#xff0c;可以得到最大的覆蓋范圍。如果覆蓋范圍能達到最后一個元素&#xff0c;就是全局最優 &#xff0…

05.查詢表

查詢表 字段顯示可以使用別名: col1 AS alias1, col2 AS alias2, … WHERE子句:指明過濾條件以實現“選擇"的功能: 過濾條件: 布爾型表達式算術操作符:,-,*,/,%比較操作符:,<>(相等或都為空),<>,!(非標準SQL),>,>,<,<范圍查詢: BETWEEN min_num …