著作一覧 |
仕事で、ISO-8583っぽいデータを扱っていて、ちょっと考えた。
データフォーマットにはいくつかの種類がある。それぞれ良い点、悪い点がある。ここでは特にAPIに関して考える。
大雑把には、以下の4種類にわかれる。
以下では、次のデータを扱うことを例とする。
データベースのスキーマで示す。
フィールド名 | データ型 |
---|---|
id | number(16) |
name | varchar(64) |
nationality | char(3) |
最初の固定長テキストというのは、レコードとそれを構成するフィールドの長さが決まっているものだ。2レコードの例を示す。
static String TEST_DATA = "0000000000000001YAMADA KAKASHI JPN" + "0000000000000012BOOTY POOTY POING POING BOB";
メリットは全然ない。というのは嘘で、COBOLで扱うには非常にありがたい。
* うろ覚えなので雰囲気だけ。 03 record indexed by poyopoyo. 05 id 9(16) display. 05 name x(64) display. 05 nationality x(3) display.
それを除けば、スペース効率は悪いし(したがって圧縮しなければ転送効率も悪い)特に可視性に優れいているわけでもない。致命的なのは拡張(フィールドの追加やフィールド長の変更)に耐えられない点で、そのため、通常は山ほどフィラーを取ることになる。そのため、ますますスペース効率は悪くなる。
もっとも、Cプログラマにも、扱いやすい。
char id[16 + 1]; // 16とかは#define FIELD_ID_LENGTH 16とかするのかも。 char name[64 + 1]; char* record = read_file(); memcpy(id, record, 16); id[16] = 0; memcpy(name, record + 16, 64); ... record += 64 + 16 + 3;
次のバイナリ固定長も基本は同様だ。ただ、多少のスペース効率が得られたりする場合がある。上の例だと、idを2進化10進数にするなどが考えられる。
static String TEST_DATA = "\0\0\0\0\0\0\0\x01YAMADA KAKASHI JPN" + "\0\0\0\0\0\0\0\x12BOOTY POOTY POING POING BOB";
固定長に比べて可変長の場合、フォーマットは実にさまざまだ。以下ではテキストに限定して主なものを示す。
CSV(カンマ区切り値形式)
static String TEST_DATA = "1,YAMADA KAKASHI,JPN\r\n" + "12,BOOTY POOTY POING POING,BOB\r\n";
一気に、スペース効率が良くなった。また、仮に128文字のnameフィールドのサポートが必要となったとしても、バリデーション部分を除けば、修正が不要な点も長所となる。
APIとしてはCOBOLでは突然扱いにくくなるが、Cなら普通に使える(もっとも固定長のほうがやはり楽ではある)。またライブラリが充実しているJavaやC#なら何も考えなくても、正規化されている前提であれば、簡単に利用できる。
String[] records = buffer.split("\r\n"); for (String r : records) { String[] fields = r.split(','); ... }
実際には、正規化されていなかったり、nameのようなフィールドが「George, Boochi-the-third "family-man" Washington」だったりするので、エスケープが必要になったりする。たとえば、全体を"で囲み、フィールド内で出現する"は""のように記述するなどだ。
CSVのバリエーションとして、タブ(\t)で区切るというものも最近はある。さすがに制御コードがテキスト内に現れることはないので、上で説明したエスケープが不要になるといったメリットがある。
ここまでを、牧歌時代のフォーマットと考える。というのは、特別なライブラリがなくても、プログラミング言語の機能だけで誰でも処理できるからだ。もっとも、CSVでもありとあらゆる形式をサポートしようとすると格段にハードルが上がるため、ライブラリサポートが欲しくなるかも知れない。
で、書くのが面倒になってしまったが、XMLとBitmapがこの後に続く。
XMLは(無理やり正規表現という方法もあるが)既存パーサを使うことになる。つまりすでにプログラミング言語の機能だけで処理できる範囲を超えた、と考えても良い(実際には、汎用パーサを提供できるということなのだが、アプリケーションからみれば同じことだ)。
で、本題のBitmapだが、パーサというわけにはいかない。というのは、ビットマップを使うという点では共通だとしても、個々のビットの意味がスキーマによってまったく異なるからだ。しかし、正規化されたCSVほど、単純にプログラミング言語の機能だけで処理できるわけでもない。というよりも、そういうところで努力したコードは見たくない。
Bitmapは、たとえば以下のように定義される。
レコードの先頭4バイトをビットマップとする。MSBをビット1とする。ビット1はidフィールドを示す。idフィールドは最大16バイトでASCIIの'0'から'9'までの文字で構成される。先頭2バイトで長さを示す。ビット2はnameフィールドを示す。最大64バイトでASCIIの印字可能文字で構成される。先頭2バイトで長さを示す。ビット3は3バイトでASCIIの'A'から'Z'で構成される。
以下に例を示す。なお、2レコード目はなぜかnameフィールドが欠けたレコードである(Bitampはフィールドの不在を示せるという特徴がある)。
static String TEST_DATA = "\xe0\0\0\0" "01114YAMADA KAKASHIJPN\r\n" + "\xa0\0\0\0" "012CHN", + "\xe0\0\0\0" "021224BOOTY POOTY POING POINGBOB\r\n";
結論を書くと、こういう形式こそDSLがふさわしい。
正確には、DSLを構成するクラス(群)はライブラリとして提供できるのだが、直接プログラム言語のプロシージャから操作するのではなく、一度、データ定義としてDSLでスキーマを記述できるようにライブラリを構成すべきということだ。
たとえば、固定長フィールドはフィールド名と長さと構成文字種(Nは\dをAは\wを示すとする)、可変長フィールドは長さを示すLの数でフィールドを示すとすると、アプリケーションは以下のように記述する。ビットを引数で示すか、順序で示すかは好みだと思うが、以下では引数で明示している。
BitmapParser fbp = BitmapParserBuilder .define(4) // Bitmap長を指定 .FIX(1, "id", 16, 'N') .LL(2, "name", 64, 'A') .FIX(3, "nationarity", 3, 'N'); // .endDefine() があったほうが固い。 ... fbp.read(new File(path)); while (fbp.next()) { System.out.println("id=" + fbp.bit(1) + ", name=" + fbp.bit(2) + ", natinality=" + fbp.bit(3)); // bitメソッドの型はObjectになる。またはJDBCのように、getString(2)などの型指定の取得メソッドを用意する。 }
実装例はまた来週。
上の分類だとXMLの仲間。ビットマップの異文化っぷりを楽しむというのがテーマ。
ジェズイットを見習え |