Protocol Bufferを使ったデータのシリアライズ

ではいよいよ、Protocol Bufferを用いたプログラミングを行っていこう。

Javaオブジェクトをシリアライズしてファイルに保存し、そのファイルからオブジェクトを復元する、というサンプルを通じて、Protocol Bufferに関する知識を深めていきたい。

Protocol Bufferを用いた開発の流れは、だいたい以下のようになる。この流れに沿って解説を進めていくこととする。

  1. protoファイルの記述
  2. protocによるコンパイルを行い、コードの自動生成を行う
  3. 自動生成されたコードを用いてプログラミング

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が内部的に使用するため指定してはいけない。