Protocol Bufferを使ったデータのシリアライズ
ではいよいよ、Protocol Bufferを用いたプログラミングを行っていこう。
Javaオブジェクトをシリアライズしてファイルに保存し、そのファイルからオブジェクトを復元する、というサンプルを通じて、Protocol Bufferに関する知識を深めていきたい。
Protocol Bufferを用いた開発の流れは、だいたい以下のようになる。この流れに沿って解説を進めていくこととする。
- protoファイルの記述
- protocによるコンパイルを行い、コードの自動生成を行う
- 自動生成されたコードを用いてプログラミング
protoファイルの記述
Protocol Bufferを用いてデータのシリアライズを行うには、まず対象となるデータの構造を示した「protoファイル」を作成する必要がある。
protoファイルは、拡張子が「.proto」となるファイルで、独自の記述言語を用いてデータ構造を記述する。データ構造は「メッセージタイプ」と呼ばれ、一つのprotoファイル内に複数記述することができる。
protoファイル内に記述する、メッセージタイプの書式は次のようなものだ。
message メッセージの型 {
フィールドルール フィールド型 フィールド名 = タグ;
...
}
message
というキーワードに続けてメッセージタイプの名前を記述し、中括弧で囲まれたブロックを作ればメッセージタイプを作成できる。ブロック内には、データのメンバとして「フィールド」を0個以上定義する。
例えば、名前と年齢をフィールドに持つ「Person」というメッセージタイプを定義するには、次のようにする。
リスト: Persons.proto
message Person {
// 名前を表す。必須
required string name = 1;
// 年齢を表す
optional int32 age = 2;
}
では、フィールドの定義方法について詳しく見ていこう。
フィールドルール
フィールドルールには、以下の3つがある。
ルール | 意味 |
---|---|
required | 必須 |
optional | 省略可能 |
repeated | 要素の繰り返し |
公式ドキュメントによれば、requiredルールを用いて一度必須にしたものを取り消すのには非常なリスクを伴うので、Google社内ではあまり好まれないそうだ。
フィールドの型
フィールドの型には数値や文字列があり、シリアライズされたデータのサイズなどにも密接に関わってくる。従って、どのような型を指定するかは非常に重要だ。
現在利用できるフィールド型には以下のようなものがある(以下の表は、Protocol BuffersのLanguage Guideを元に作成)。
型 | 説明 | C++での型 | Javaでの型 |
---|---|---|---|
double | 64ビット浮動小数点数 | double | double |
float | 32ビット浮動小数点数 | float | float |
int32 | 符号付き32ビット整数 | int32 | int |
int64 | 符号付き64ビット整数 | int64 | long |
uint32 | 符号なし32ビット整数 | uint32 | int |
uint64 | 符号なし64ビット整数 | uint64 | long |
sint32 | 符号付き32ビット整数。負数の表現はint32よりも効率的 | int32 | int |
sint64 | 符号付き32ビット整数。負数の表現はint64よりも効率的 | int64 | long |
fixed32 | 符号なし32ビット整数。常に4バイト使用 | uint32 | int |
fixed64 | 符号なし64ビット整数。常に8バイト使用 | uint64 | long |
sfixed32 | 符号付き32ビット整数。常に4バイト使用。負数の表現はfixed32よりも効率的 | int32 | int |
sfixed64 | 符号付き64ビット整数。常に8バイト使用。負数の表現はfixed64よりも効率的 | int64 | long |
bool | true/falseをとる値 | bool | boolean |
string | 文字列 | string | java.lang.String |
bytes | バイト列 | string | com.google.protobuf.ByteString |
intXX、uintXX、sintXXといった数値型は、値となる数値の大きさに応じて、シリアライズされたときのバイト長が変化する。小さな数値であれば短いバイト長しか必要としないので、データがさらにコンパクトになるという訳だ。対して、fixedXXやsfixedXXは常に決まったバイト数を消費し、非常に大きな数値を表現する場合は効率的だ。予想される数値の大きさに応じて、これらの型を使い分けると良い。
タグ
「required string name = 1;」のようなフィールド定義において、フィールド名に続けて、イコールを挟んで指定する数値(ここでは1)は、タグと呼ばれるものである。
タグはProtocol Bufferにおいて非常に重要な役目を果たし、一度指定したら、絶対に変更してはならない。(バイナリ内で、この数値がフィールドのキーとして用いられるため)。
タグには任意の整数値を指定することができるが、他のフィールドと重なることは許されない。また、19,000から19,999の間の数値は、Protocol Bufferが内部的に使用するため指定してはいけない。