今回はバイナリファイルの特徴を生かした使い方を説明します。
まずバイナリファイルの特性とは何かから説明します。
テキストファイルはデータを文字として保存します。
例えば数値の123は文字の"123"としてファイルに保存します。
バイナリファイルはデータを2進数でバイト単位に保存します。
同じく数値の123は 00000000 00000000 00000000 01111011 というビットデータで保存されます。
テキストファイルで12345を保存すると"12345"として保存します。
123と比べて2桁増えています。1文字は8ビット(1バイト)ですので、桁が増えると保存するデータ量が増えていきます。
バイナリファイルで12345を保存すると 00000000 00000000 00110000 00111001 です。
長く見えますが123の時とバイト数は変わっていません。
テキストファイルは1個のデータのサイズが変わりますが、バイナリファイルは変わらないというところがポイントです。
下のプログラムを動かし、バイナリファイルを作成します。
5人分の名前と年齢のデータが構造体配列に入っています。
これを「People.ppl」という独自に作った拡張子のファイルに保存します。
※「txt」では無くなるため、ダブルクリックでメモ帳は開かなくなります。
<sample program 125-01>
#include <stdio.h> #define PERSON_MAX 5 #define NAME_MAX 21 typedef struct{ char name[NAME_MAX]; int age; } Person; int main(void) { Person people[PERSON_MAX] = { { "Alex", 21 }, { "Billy", 25 }, { "Charlie", 30}, { "Dennis", 42 }, { "Eddy", 48 } }; FILE* fp; fp = fopen("People.ppl", "wb"); if (fp == NULL) { printf("OPEN ERROR\n"); return 1; } fwrite(people, sizeof(Person), PERSON_MAX, fp); fclose(fp); return 0; } |
実行して、「People.ppl」を作成します。
※中身が見たい方は、先にメモ帳を開いておき、「People.ppl」をドラッグドロップしてください。
次に読み込みのプログラムを作るのですが、今回やりたいことを書きます。
全てのデータを読み込むのではなく、こちらで指定したデータを1つだけ読み込みたいです。
まずは枠組みを作りましょう。
<sample program 125-02>
#include <stdio.h> #define PERSON_MAX 5 #define NAME_MAX 21 typedef struct{ char name[NAME_MAX]; int age; } Person; int main(void) { Person person; FILE* fp; fp = fopen("People.ppl", "rb"); if (fp == NULL) { printf("OPEN ERROR\n"); return 1; } fclose(fp); return 0; } |
まだ読み込み部分は入れていません。
これまでのプログラムで、ファイルは先頭から読み込まれることを見てきたと思います。
テキストファイルでは区切り文字があり、読み込み用の関数が呼ばれる度にファイルに入っているデータが順次読み込まれました。
バイナリファイルでは区切り文字はありませんが、バイト数を区切って順番に読み込むことも出来ます。
ファイルの中には、次に読み書きするデータの位置を保持している場所があり、データを読み込む度に自動的に次のデータを指していきます。
その「読み込むデータの位置」を変える関数が「fseek関数」です。
<fseek関数>
fseek(@, A, B); @ファイルポインタ A基準位置からのバイト数 B基準位置 基準位置 SEEK_SET ファイルの先頭 SEEK_CUR 現在の位置 SEEK_END ファイルの終わり |
この関数を使えば、読み込む前に位置を変更することが出来ます。
取りあえず、2人目のデータを読み込むプログラムを追加してみます。
<sample program 125-03>
#include <stdio.h> #define PERSON_MAX 5 #define NAME_MAX 21 typedef struct{ char name[NAME_MAX]; int age; } Person; int main(void) { Person person; FILE* fp; fp = fopen("People.ppl", "rb"); if (fp == NULL) { printf("OPEN ERROR\n"); return 1; } fseek(fp, sizeof(Person), SEEK_SET); fread(&person, sizeof(Person), 1, fp); fclose(fp); printf("Name = %s\n", person.name); printf("Age = %d\n", person.age); return 0; } |
<実行結果>
Name = Billy Age = 25 続行するには何かキーを押してください・・・
1人目のAlexは読まれず、2人目のBillyのデータが読み込まれました。
SEEK_SETがファイルの先頭を表しますから、先頭から1人分(sizeof(Person))後へ読み込み位置を変更しました。
冒頭で書きましたが、格納したデータによって1人分のデータサイズが変わることはありませんので、それぞれのデータの先頭番地に移動させることが出来るのです。
それでは、指定した番号(0番から4番)のデータを読み込めるよう変更してみましょう。
<sample program 125-04>
#include <stdio.h> #define PERSON_MAX 5 #define NAME_MAX 21 typedef struct{ char name[NAME_MAX]; int age; } Person; int main(void) { Person person; int input; FILE* fp; fp = fopen("People.ppl", "rb"); if (fp == NULL) { printf("OPEN ERROR\n"); return 1; } do { scanf("%d", &input); } while (input < 0 || input >= PERSON_MAX); fseek(fp, sizeof(Person) * input, SEEK_SET); fread(&person, sizeof(Person), 1, fp); fclose(fp); printf("Name = %s\n", person.name); printf("Age = %d\n", person.age); return 0; } |
<実行結果>
3 Name = Dennis Age = 42 続行するには何かキーを押してください・・・
ファイルを開いたまま、必要な時に必要なデータを読み込むことも可能です。
工夫すれば色々な場面で使えそうですね。