gob パッケージは,エンコーダ (送信器) とデコーダ (受信器) 間で交換されるバイナリ値である gob のストリームを管理します。
典型的な使用例は,"net/rpc" パッケージで提供されるようなリモートプロシージャコール (RPC) の引数と結果を渡すときに使います。
この実装は,ストリーム内の各データ型のカスタムコーデックをコンパイルします。
1 つのエンコーダを使用して値のストリームを送信し,コンパイルのコストを削減する場合に最も効率的です。
基本
gob の流れは自己記述的です。
ストリーム内の各データ項目の前には,その型の指定があります。
これは,事前定義済みの型の小さなセットで表されます。
ポインタは送信されませんが,それらが指すものは送信されます。
つまり,値が平坦化されます。
値がないため, Nil ポインタは許可されていません。
再帰型は問題なく動作しますが,再帰的な値 (周期を含むデータ) は問題があります。
これは変わるかもしれません。
gob を使用するには,エンコーダを作成し,それに一連のデータ項目を値またはアドレスとして参照できるアドレスとして表示します。
エンコーダは,すべての型情報が必要になる前に送信されるようにします。
受信側では, Decoder がエンコードされたストリームから値を取得し,それらをローカル変数に展開します。
型と値
転送元と転送先の値 / 型は正確に一致する必要はありません。
構造体の場合,ソース内にあるが受け取り変数にないフィールド (名前によって識別される) は無視されます。
受信変数にはあるが送信型または値に欠落しているフィールドは,宛先では無視されます。
同じ名前のフィールドが両方に存在する場合,それらの型は互換性がなければなりません。
レシーバとトランスミッタの両方が,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 } // 共通するフィールド名なし
整数は 2 つの方法で転送されます。
任意精度の符号付き整数または任意精度の符号なし整数。
gob フォーマットには int8, int16 などの区別はありません。
符号付き整数と符号なし整数のみがあります。
後述するように,送信機は可変長符号化で値を送信します。
受信側はその値を受け取り,それを変換先変数に格納します。
浮動小数点数は,常に IEEE-754 64 ビット精度を使用して送信されます (下記参照) 。
符号付き整数は,任意の符号付き整数変数 (int, int16 など) で受け取ることができます。
符号なし整数は,任意の符号なし整数変数で受け取ることができます。
浮動小数点値は,任意の浮動小数点変数で受け取ることができます。
ただし,変換先変数は値を表すことができなければならず,そうでなければデコード操作は失敗します。
構造体,配列,スライスもサポートされています。
構造体はエクスポートされたフィールドのみをエンコードおよびデコードします。
文字列とバイト配列は,特別で効率的な表現でサポートされています (下記参照) 。
スライスがデコードされるとき,既存のスライスが容量を持っていれば,スライスはその場で拡張されます。
そうでなければ,新しい配列が割り当てられます。
結果のスライスの長さはデコードされた要素の数を報告します。
一般に,割り当てが必要な場合,デコーダはメモリを割り当てます。
そうでない場合は,ストリームから読み取った値で変換先変数を更新します。
それらが最初に初期化されないので,変換先が map, struct ,または slice などの複合値である場合,デコードされた値は要素ごとに既存の変数にマージされます。
関数とチャンネルは gob で送信されません。
最上位レベルでそのような値をエンコードしようとしても失敗します。
chan または func 型の構造体フィールドは,エクスポートされていないフィールドとまったく同じように扱われ,無視されます。
Gob は, GobEncoder または encoding.BinaryMarshaler インターフェースを実装している任意の型の値を,対応するメソッドをその優先順に呼び出してエンコードできます。
Gob は, GobDecoder または encoding.BinaryUnmarshaler インターフェースを実装している任意の型の値を,この優先順で対応するメソッドを呼び出すことによってデコードできます。
エンコーディングの詳細
このセクションでは,エンコーディング,ほとんどのユーザーにとって重要ではない詳細について説明します。
詳細はボトムアップで表示されます。
符号なし整数は, 2 つの方法のうちの 1 つで送信されます。
128 未満の場合は,その値を持つバイトとして送信されます。
それ以外の場合は,値を保持する最小長のビッグエンディアン (上位バイト先頭) バイトストリームとして送信され,その後にバイトカウントを保持する 1 バイトが否定されます。
したがって,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 は 3 バイトのみでエンコードされています (FE 31 40) 。
バイトの文字列とスライスは,符号なしのカウントとして送信され,その後に多くの未解釈の値のバイトが続きます。
他のすべてのスライスと配列は,符号なしのカウントとして送信され,その後にその型に標準の gob エンコーディングを使用している多くの要素が再帰的に送信されます。
マップは符号なしのカウントとして送信され,その後に多数のキーと要素のペアが続きます。
空だが nil 以外のマップが送信されるので,受信側がまだ割り当てていない場合は,送信されたマップが nil でトップレベルでない限り,常に受信時に割り当てられる。
スライスや配列,さらにはマップでは,すべての要素がゼロであっても,ゼロ値の要素であってもすべての要素が転送されます。
構造体は一連の (フィールド番号,フィールド値) ペアとして送信されます。
フィールド値は,その型の標準gob 符号化を使用して再帰的に送信されます。
フィールドの型がゼロ値の場合 (配列を除く。
上記を参照) ,送信から省略されます。
フィールド番号は,エンコードされた構造体の型によって定義されます。
エンコードされた型の最初のフィールドはフィールド 0 , 2 番目のフィールドはフィールド 1 などです。
値をエンコードする場合,効率のためにフィールド番号はデルタエンコードされます。
フィールド番号の昇順に送信されます。
したがって,デルタは符号なしです。
デルタエンコーディングの初期化はフィールド番号を -1 に設定するので,値 7 の符号なし整数フィールド 0 が符号なしデルタ = 1 ,符号なし値 = 7 ,または (01 07) として送信されます。
最後に,すべてのフィールドが送信された後,終了マークは構造体の終わりを示します。
そのマークは delta=0 の値で,表現は (00) です。
インターフェース型は互換性についてチェックされません。
すべてのインターフェース型は,伝送のために, int または []byte に類似した 1 つの " インターフェース " 型のメンバーとして扱われます - 実際,それらはすべて interface{} として扱われます。
インターフェース値は,送信される具象型を識別するストリング (Register を呼び出すことによって事前定義されている必要がある名前) として送信され,その後に続くデータの長さのバイトカウントが続きます。
interface 値に格納された具象 (動的) 値の通常のエンコードが続きます。
受信時に,デコーダは,展開された具象アイテムが受信変数のインターフェースを満たすことを確認します。
値が Encode に渡され,型が構造体 (または構造体へのポインタなど) ではない場合,処理を簡単にするために 1 つのフィールドの構造体として表されます。
これの唯一の目に見える影響は,エンコードされた構造体の最後のフィールドの後のように,値の後にゼロバイトをエンコードすることで,デコードアルゴリズムはトップレベルの値がいつ完了するかを認識します。
型の表現については後述します。
型がエンコーダとデコーダの間の特定の接続で定義されている場合は,符号付き整数型の ID が割り当てられます。
Encoder.Encode(v) が呼び出されると, v の型とそのすべての要素に割り当てられた ID があることを確認してから,ペア (typeid, encoded-v) を送信します。
ここで, typeid は符号化型の型 ID です。
of v および encoded-v は,値 v のgob 符号化です。
型を定義するために,エンコーダは未使用の正の型 ID を選択し,ペア (-type id, encoded-type) を送信します。
encoded-type は,これらの型から構成された wireType 記述の gob エンコーディングです。
type wireType struct {
ArrayT *ArrayType
SliceT *SliceType
StructT *StructType
MapT *MapType
GobEncoderT *gobEncoderType
BinaryMarshalerT *gobEncoderType
TextMarshalerT *gobEncoderType
}
type arrayType struct {
CommonType
Elem typeId
Len int
}
type CommonType struct {
Name string // 構造体型の名前
Id int // 型のID, repeated so it's inside the type
}
type sliceType struct {
CommonType
Elem typeId
}
type structType struct {
CommonType
Field []*fieldType // 構造体のフィールド
}
type fieldType struct {
Name string // フィールド名
Id int // フィールドの型ID, すでに定義されていなければならない
}
type mapType struct {
CommonType
Key typeId
Elem typeId
}
type gobEncoderType struct {
CommonType
}
入れ子になった型 ID がある場合は,最上位の型 ID を使用して encoded-v を記述する前に,すべての内部型 ID の型を定義する必要があります。
セットアップを簡単にするために,接続はこれらの型を先験的に理解するように定義されています。
また,基本的な gob 型 int, uint などもそうです。
bool 1
int 2
uint 3
float 4
[]byte 5
string 6
complex 7
interface 8
// 予約 ID のためのギャップ
WireType 16
ArrayType 17
CommonType 18
SliceType 19
StructType 20
FieldType 21
// 22 は fieldType のスライス
MapType 23
最後に, Encode の呼び出しによって作成された各メッセージの前には,メッセージ内に残っているバイト数の符号化された符号なし整数カウントが続きます。
初期型名の後,インターフェース値は同じ方法でラップされます。
実際には,インターフェース値は Encode の再帰呼び出しのように機能します。
まとめると,gob ストリームは次のようになります。
(バイト数 (-型 ID, wireType のエンコード)* (型 ID, 値のエンコード))*
* は 0 回以上の繰り返しを表し,値の型 ID は事前定義されているか,ストリーム内の値の前に定義されている必要があります。
互換性 : パッケージに対する将来の変更は,以前のバージョンを使用してエンコードされたストリームとの互換性を維持するように努めます。
つまり,このパッケージのどのリリース版でも,セキュリティ修正などの問題を条件として,以前のリリース版で書き込まれたデータをデコードできるはずです。
背景については Go 互換性文書を参照してください : https://golang.org/doc/go1compat
gob ワイヤーフォーマットのデザインディスカッションについては, "Gobs of data" を参照してください。
https://blog.golang.org/gobs-of-data
▾ 例 (Basic)
この例は,パッケージの基本的な使い方を示しています。
エンコーダを作成し,いくつかの値を送信し,それらをデコーダで受信します。
コード:
package gob_test
import (
"bytes"
"encoding/gob"
"fmt"
"log"
)
type P struct {
X, Y, Z int
Name string
}
type Q struct {
X, Y *int32
Name string
}
func Example_basic() {
var network bytes.Buffer
enc := gob.NewEncoder(&network)
dec := gob.NewDecoder(&network)
err := enc.Encode(P{3, 4, 5, "Pythagoras"})
if err != nil {
log.Fatal("encode error:", err)
}
err = enc.Encode(P{1782, 1841, 1922, "Treehouse"})
if err != nil {
log.Fatal("encode error:", err)
}
var q Q
err = dec.Decode(&q)
if err != nil {
log.Fatal("decode error 1:", err)
}
fmt.Printf("%q: {%d, %d}\n", q.Name, *q.X, *q.Y)
err = dec.Decode(&q)
if err != nil {
log.Fatal("decode error 2:", err)
}
fmt.Printf("%q: {%d, %d}\n", q.Name, *q.X, *q.Y)
}
▾ 例 (EncodeDecode)
この例では,カスタムのエンコード方法とデコード方法を実装する値を送信します。
コード:
package gob_test
import (
"bytes"
"encoding/gob"
"fmt"
"log"
)
type Vector struct {
x, y, z int
}
func (v Vector) MarshalBinary() ([]byte, error) {
var b bytes.Buffer
fmt.Fprintln(&b, v.x, v.y, v.z)
return b.Bytes(), nil
}
func (v *Vector) UnmarshalBinary(data []byte) error {
b := bytes.NewBuffer(data)
_, err := fmt.Fscanln(b, &v.x, &v.y, &v.z)
return err
}
func Example_encodeDecode() {
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 Vector
err = dec.Decode(&v)
if err != nil {
log.Fatal("decode:", err)
}
fmt.Println(v)
}
▾ 例 (Interface)
この例は,インターフェース値をエンコードする方法を示しています。
通常の型との主な違いは,インターフェースを実装する具象型を登録することです。
コード:
package gob_test
import (
"bytes"
"encoding/gob"
"fmt"
"log"
"math"
)
type Point struct {
X, Y int
}
func (p Point) Hypotenuse() float64 {
return math.Hypot(float64(p.X), float64(p.Y))
}
type Pythagoras interface {
Hypotenuse() float64
}
func Example_interface() {
var network bytes.Buffer
gob.Register(Point{})
enc := gob.NewEncoder(&network)
for i := 1; i <= 3; i++ {
interfaceEncode(enc, Point{3 * i, 4 * i})
}
dec := gob.NewDecoder(&network)
for i := 1; i <= 3; i++ {
result := interfaceDecode(dec)
fmt.Println(result.Hypotenuse())
}
}
func interfaceEncode(enc *gob.Encoder, p Pythagoras) {
err := enc.Encode(&p)
if err != nil {
log.Fatal("encode:", err)
}
}
func interfaceDecode(dec *gob.Decoder) Pythagoras {
var p Pythagoras
err := dec.Decode(&p)
if err != nil {
log.Fatal("decode:", err)
}
return p
}