第36回 プログラミングについて 『バイナリファイルを扱ってみよう! その2』

『バイナリファイルを扱ってみよう! その2』
前回はとにかくバイナリファイルを作ってみようということでお話をしました。今回は、可変長レコードと固定長レコードを考えてみましょう。
DOSやUNIXのファイルシステムではファイル内のレコードの形式については、ファイル自体にはその情報は持っていません。あくまでもプログラムがそれらのハンドリングを責任をもって行なうという思想になっています。VMSなどではかなり細かくレコードやアクセス方法についての情報をファイル自体が持つようになっています。ここでは、プログラムが責任もってそれらをハンドリングすることについて考えていきます。
まずバイナリファイルの可変長レコードについて考えてみましょう。
ストリーム形式のテキストファイルの場合は、テキスト自体にはヌルや改行文字が入っていないということが前提になっていますので、1つのレコード(1行)の終わりに改行コードなどを挿入することで区切りにすることができます。それに対してバイナリファイルの場合にはすべての文字がデータになりますので、ストリーム形式のように区切り文字を挿入する手は使えません。そこで、以前VMSのテキストファイルの形式で説明したようにレコードの先頭にそのレコードのバイト数を挿入する手法を使います。
ファイル内の最長のレコードの長さが255バイト以下のときには、レコード長を示すには1バイトあれば表現できます。65535バイト以下のときには2バイト、それ以上のときには4バイトを使用してレコード長を表現します。ここでは2バイトとしましょう。
00 0000000000111111111122222222223333333333
01 0123456789012345678901234567890123456789
| |---------レコードが40バイト---------|
|
|ここに40という数値を挿入
00 00000000001111111111222222222233333333334444444444
01 01234567890123456789012345678901234567890123456789
| |--------------レコードが50バイト--------------|
|
|ここに50という数値を挿入
00 0000000000111111111122222222
01 0123456789012345678901234567
| |---レコードが28バイト---|
|
|ここに28という数値を挿入
上図のように実際のレコードに先立ってレコードのバイト数をファイルに挿入することで、可変長レコードのバイナリファイルを作成することができます。この形式のファイルのときには、ファイルを読み込むときには必ずファイルの先頭から読み込まなければ、どこがレコードの先頭なのかが全然分かりませんので注意して下さい。
次の例のようにすればこの形式のファイルを読み込むことができます。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys\stat.h>
main()
{
int fd;
char data[512];
unsigned int lrec;
fd=open("binary.dat",O_RDWR|O_BINARY);
while(1){
if(read(fd,data,2)<2)break; /* レコード長を読み込む */
lrec= *(unsigned int *)data;
read(fd,data,lrec); /* 実際のレコードを読み込む */
}
close(fd);
}
この例ではそれ以上レコード長を示すデータを読み込めなかったときに読み込みを終了させていますが、どうしてもファイルの最後を示すレコードを最後に挿入したいときには、レコード長を示すデータが 65535 のときに(実際のレコードにはこの長さのものが存在しないとしたとき)読み込みを終了させるか、実際のレコード内にファイルの終了を示すデータを埋め込んでおく方法のいずれかの方法をとることになります。
次に固定長レコードのファイルを考えてみましょう。固定長レコードの場合は1つのレコードの長さが既に決まっていますので、可変長レコードのように先頭にレコードの長さを示すデータは必要がなくなります。その代わりにレコード長よりも長いデータを書き込むときには、複数レコードにまたがって書き込むことになります。このようなときにはデータのどこかに総レコード数とか次のレコードに続くなどといったことを示すデータを埋め込んでおかなければなりません。
このようにしてみると、固定長レコードは可変長レコードよりも扱いずらいようですが、固定長レコードの場合には可変長レコードにはない利点があります。それはどのレコードもレコード長の整数倍の位置にレコードの先頭があることです。ですから30番目のレコードだけを読み込んだり、40番目のレコードだけを書き直すことが可能になるのです。可変長レコードのときでも書き直すことは可能ですがレコードの長さを変化させることはできません。
任意のレコードに対してのアクセルが可能ということは、レコードのランダムアクセスが可能であるということです。ランダムにレコードをアクセスするには、C言語の関数に lseek というものがあります。この関数は固定長レコードのファイルに対してのみ使用できるというものではなく、 open 関数でオープンしたファイルならばどんなファイルに対しても使用することができます。
いまファイルが40バイトの固定長レコードで構成されているとしたとき、30番目のレコードをアクセスしたいときには、
#define RECORD_SIZE 40
lseek(fd,(30-1)*RECORD_SIZE,SEEK_SET);
と記述すると、30番目のレコードの先頭の位置にファイルポインタが移動します。その後、レコードを読み込んだり、書き込んだりします。実際にはこの関数に渡すのはオフセット値ですので、1番目のレコードはオフセット0、2番めのレコードは40になりますので、N番目のレコードは(N-1)*40となる訳です。
固定長レコードのファイルは、私の場合はライブラリファイルなどを作成するときに使用しています。例えばCADソフトなどの場合、図形部品をライブラリの中に書き込んでおいて実際に使用するときにライブラリファイルから読み込むようにしています。
このようなとき、図形部品の形状や規模は様々ですので、1つの固定長レコードでは表現はできません。このようなときには次のように図形部品レコードを設計します(次に示すものはあくまでも例ですので、常にこの形式とまったく同じではありません)。
図形部品ヘッダレコード:
00000000001111111 111222222222233333333 33
01234567890123456 789012345678901234567 89
| 図形部品名 | |レコード数
必ずヘッダレコードに類するレコードを作り、そこには後に続く図形データのレコード数(ヘッダレコードの数も含めても構いません)を書き込むようにします。このようにすると、この図形部品のヘッダレコードを読み込むと次の図形部品のヘッダレコードの位置が分かりますので、ヘッダレコードのみを次々に読み込んでライブラリ内に書き込まれている図形部品の名称を抽出することも無駄なくできるようになる訳です。
ヘッダレコードのみを別のファイルに書き込む方法もありますが、この場合は確かに図形部品を検索するのは簡単で高速にはなりますが、ライブラリファイルとして考えたときには、ライブラリからの削除や追加という処理をするときにはプログラムをつくるのが面倒になりますので、私の場合はあまりこの方法は使用していません。
今回は少しでしたが、また次回。