パソコン活用研究5番街(Visual Basic、Excel(VBA)、BASIC プログラミング研究)

ファイルの操作(ランダムファイル)

--------------------------------------------------------------------------------
シーケンシャルファイルの取り扱い方については、ファイル操作(シーケンシャルファイル)で説明
しました。今回は、ランダムファイルの取り扱いを説明します。はじめてファイル操作を学ぶ場合は、
まず、ファイル操作(シーケンシャルファイル)でファイル操作の基本について、おさえて下さい。
--------------------------------------------------------------------------------

1 シーケンシャルファイルの弱点
まず、シーケンシャルファイルの弱点についてお話します。シ−ケンシャルファイルはランダムファイル
より取り扱いがシンプルですが、途中のデータの一部だけを修正して書き込むということができない、
という弱点があります。

例えば、10個のデータがあるとして、7番目のデータだけを修正したいとします。すると、シーケンシャル
ファイルの場合は以下の手順になります。
(1) ファイルをOpenする。
(2) 10個のファイルを全て読み込む。
(3) 7番目のデータを修正する。
(4) 10個のファイルをあらためて書き込む。

そうです、シーケンシャルファイルは、データを順番に読み出すことしかできないので、7番目のデータだけ
を取り出して修正して書き込む、ということができません。7番目のデータにアクセスするには、1〜6番目の
データを順に読み出してからでないとアクセスできないのです。結局、7番目だけ修正するのに、全部読み出し
て一部修正したら、全部書き込むという手順が必要になります。
まあ、100個程度のデータなら、これもたいした手間ではありませんが、1万個のデータとなると、かなりムダ
な作業をすることになります。


2 ランダムファイルの有利な点と弱点
ランダムファイルは、上記の例でいうと、7番目のデータだけを取り出して修正して書き込む、といことが可能
になります。1万個のデータの中から1個だけ修正するというケースでは、ランダムファイルが圧倒的に有利
になります。

ただし、そのランダムファイルの有利な点を実現するために、ランダムファイルでは1レコード(氏名、年齢、
住所、電話番号のようなひとつの関連データの固まり)の大きさが固定で決められています。なぜなら、
1レコードの大きさがまちまちだったら、例えば、7番目のレコード、8番目のレコードがどこからどこまでか
判別できなくなってしまいますから。1レコードの大きさは通常256バイトに決められていますが、大きさが
固定されれば、7番目のデータは、256*6〜256*6+255バイト目までにあると(先頭を0バイト目とする)判別
できます。

さて、ランダムファイルの弱点(欠点)ですが、1レコードを固定することにより、領域をムダに使うことになり
ます。例えば、1レコード(256バイト)のなかで、氏名(20バイト)、年齢(6バイト)しか使用しなければ、残り
230バイトは未使用のままでムダになってしまいます。

1レコードめ 氏名(20バイト) 年齢(6バイト) 未使用(230バイト)・・・・・・・・・・・・・・・・・
2レコードめ 氏名(20バイト) 年齢(6バイト) 未使用(230バイト)・・・・・・・・・・・・・・・・・
3レコードめ 氏名(20バイト) 年齢(6バイト) 未使用(230バイト)・・・・・・・・・・・・・・・・・
4レコードめ 氏名(20バイト) 年齢(6バイト) 未使用(230バイト)・・・・・・・・・・・・・・・・・

(表1)

まあ、今の大容量HDDの時代には領域のムダなんて関係ないとは思いますが。

3 フィールド
上記の例でみると、各レコードは、氏名(20バイト)、年齢(6バイト)に区切られています。このように
氏名(20バイト)、年齢(6バイト)に区切ることをフィールドを区切るといいます。従って、ランダムファイル
における最小単位のひとつひとつのデータはこのフィールド単位ということになります。
ランダムファイル特有の表現になりますので、覚えておいて下さい。

4 ランダムファイルの基本操作
(1) オープン
ランダムファイルをオープンするときのOPEN文の書式は、BASICによって若干異なります。

一番メジャーと思われるN88Basicでは、「FOR モード」 を指定しません。
OPEN "File Name" AS #1
のように指定します。

F-Basicでは、モードにRを指定し、
OPEN "R", #1, "File Name"
のようになります。

(2) FIELD文でファイルバッファに文字変数(フィールド変数)を割り当てる。

ここが、シーケンシャルファイルとランダムファイルが大きく違う部分になります。ランダムファイルを扱う
場合は、1レコード(通常256バイト)を分割して(分割された領域はフィールドと呼ばれます)、それぞれの
フィールドに文字変数を割り当てます。この文字変数はフィールド変数と呼ばれます。
例えば、表1の例のように氏名(20バイト)、年齢(6バイト)の領域を割り当てる場合は、
FIELD #1, 20 AS Name$, 6 AS Age$
のように記述します。
FIELD文の書式はN88BasicもF-Basicも同じです。おそらく他のBasicでも同じでしょう。
なお、フィールドの大きさの合計は当然ながら256バイト以内でないといけません。

FIELD #<ファイル番号>, フィールドの大きさ AS フィールド変数, ・・・・

ここで、ファイルバッファにフィールド変数を割り当てる、と書いていることに注意しておいて下さい。
プログラムの入出力はいきなりDisk上のファイルと直接やりとりするのではなく、ファイルバッファ
を仲介にして、
プログラム <----> ファイルバッファ <---->ファイル
という形で行われます。ここらへんの事情はファイルバッファを参考にして下さい。

(3) ファイルバッファにデータをセット(書き込みの場合)
書き込みの場合、プログラムはまずファイルバッファにデータをセットします。さて、上記の表1のように
氏名として20バイトの領域を割り当てた場合、氏名が短くて領域が余ったり、長すぎて領域が不足
(そんなに長い名前の人はいないと思いますが、ジュゲムジュゲムゴウコウノスリキリ・・・さんの場合
は圧倒的に領域不足になってしまいますね)する場合は、どうなってしまうのでしょうか。

このために、ファイルバッファへの書き出し用にLSET/RSETという命令が用意されています。
氏名が短くて領域が余る場合には、LSETだと左詰、RSETでは右詰で領域に書き込まれます。
LSET Name$ = "Taro"
RSET Name$ = "ジュゲムジュゲム ゴコウノスリキリ"

のように使います。
領域をオーバーする場合は、オーバーした分は失われます。

(4) ファイルへの書き出し
LSET/RSETでファイルバッファにデータをセットした後に、実際にファイルにデータを書き出すには、
PUT文を使います。
PUT #<ファイル番号>, レコード番号

ファイル番号で指定されたファイルバッファの内容をDiskに書き出し、指定したレコード番号がつけられます。

ちょっと整理すると、書き込みの場合は、ファイルバッファへの書き込み(LSET/RSET)と、ファイルバッファ
→ファイルへの書き込み(PUT)とは、それぞれ別に実施されることになります。

●プログラム --LSET/RSET--> ファイルバッファ --PUT--> ファイル


(5) データの読み込み
ファイルからデータを読み込む場合は、GET文を使います。
GET #<ファイル番号>, レコード番号
ファイル番号で指定されたファイルバッファに、指定されたレコード番号のデータを読み込みます。
ファイルバッファでは、FIELD文で割り当てられたとおりに、各フィールド変数にデータが自動的に
代入されます。従って、フィールド変数にわざわざ代入するような命令はありません。

●プログラム<-(FIELD文の割り当て通りに自動的にフィールド変数に代入)-ファイルバッファ<-GET-ファイル
      
(6) クローズ
ランダムファイルのクローズもCLOSE文です。
CLOSE #<ファイル番号>


5 ランダムファイル操作のプログラム例とその他のファイル操作関数
(1)プログラムの概要
本の名称、価格、備考を管理するランダムファイルを作ってみます。N88互換BASICを使います。
以下の図1のように、1レコードに5冊登録できるようにします。
この図1は、2番目のレコードの3番目のフィールドにデータを登録しているところです。
(図1)


登録し終わった画面
(図2)


コードは以下の通りです。

1 '基本形
10 width 80,25
20 open "book.dat" as #1
30 for i=1 to 5
40 field #1, 50*(i-1) as dummy$, 18 as bookname$(i), 2 as price$(i), 30 as cmt$(i)
50 next i
60 *record
70 locate 1,1:print space$(80):locate 1,1:input"開くレコードNo.(0なら終了)",n
80 if n=0 then close: end
90 if n>lof(1) then goto *field
100 gosub 200
110 *field
120 locate 1,3:print space$(80):locate 1,3:input"編集するフィールド(0なら終了)",m
130 if m=0 then put #1, n:goto *record
140 gosub 300
150 goto *field
200 'get_data
210 get #1,n
220 for i=1 to 5
225 locate 1,i+7:print space$(80)
230 locate 1,i+7:print i;" 名称 ";bookname$(i)
240 locate 27,i+7:print"価格 ";cvi(price$(i))
250 locate 40,i+7:print"備考 ";cmt$(i)
260 next i
270 return
300 'set_data
305 locate 1,5:print space$(80)
310 locate 1,5:input"名称:",book$
320 locate 27,5:input"価格",price
330 locate 40,5:input"備考",c$
335 locate 1,m+7:print space$(80)
340 locate 1,m+7:print m;" 名称 ";book$:lset bookname$(m)=book$
350 locate 27,m+7:print"価格 ";price:lset price$(m)=mki$(price)
360 locate 40,m+7:print"備考 ";c$:lset cmt$(m)=c$
370 return

(2)FIELD文も実戦的なテクニック
少し実戦的なのは、1レコードに5冊登録できるようにしているところです。1レコードを以下のように分割
しています。( )内はバイト数です。

1冊目 2冊目 3冊目 4冊目 5冊目 未使用
名称(18) 価格(2) 備考(30) 名称(18) 価格(2) 備考(30) 名称(18) 価格(2) 備考(30) 名称(18) 価格(2) 備考(30) 名称(18) 価格(2) 備考(30) (6)

このため、30〜50行のFIELD文はちょっとした工夫をしています。
forループでi=1の時は、以下のような割り当てになっています。

フィールド変数 領域のサイズ(バイト) 割り当てられた領域(0を先頭)
dummy$ 0 なし
bookname$(1) 18 0〜17
price$(1) 2 18,19
cmt$(1) 30 20〜49

i=2になった時は

フィールド変数 領域のサイズ(バイト) 割り当てられた領域
dummy$ 50 0〜49
bookname$(2) 18 50〜67
price$(2) 2 68,69
cmt$(2) 30 70〜99

ここに、ちょっとした実戦的テクニックがあるのですが、dummy$の割り当てられた領域は、良く見ると
bookname$(1), price$(1), cmt$(1)と重なっています。でも大丈夫。
FIELD文によるフィールド変数の割り当ては、1つのファイルバッファに対して何回でも実行可能で、
それらの定義は全て有効となるのです。
従って、今この状態(i=2の状態)で、dummy$には0〜49の領域のデータが代入されており、bookname$(1)
には0〜17の領域のデータが代入されています。そしてどちらも有効です。ま、dummy$は意味のない
データになりますが。
i=5までいくと、最終的にdummy$は0〜199のデータが代入されることになりますが(よく考えて
下さいね)、dummy$はまさにダミーで使ったフィールド変数なので、プログラム上で実際にdummy$
のデータを参照することはありません。このように使い捨てのdummy$をうまく使うことにより、1レコード
を有効に使うことができます。

(3) LOF
90行目で使われているLOF関数は、LOF(ファイル番号)のように使用し、ファイル番号で指定されたファイル
の最大のレコード番号を得ることができます。
90行目では、最大のレコード番号より大きなレコード番号が入力された場合は、新規のレコードと判断して、
200行目からのデータの表示のサブルーチンには行かないようにしています。

(4) MKI$,MKS$,MKD$
ランダムファイルのフィールド変数は全て文字列型なので、数値データをランダムファイルとして保存する
時には、数値データ→文字列型に変換したやらなければなりません。そのための専用の関数が、MKI$,
MKS$,MKD$です。プログラムの350行目に価格を文字列型に変換するためにMKI$を使用しています。
MKI$ 整数値を2バイト文字列に変換
MKS$ 単精度実数を4バイト文字列に変換
MKD$ 倍精度実数を8バイト文字列に変換

なお、文字列の変換の方法ですが、数値の内部表現(メモリ上の表現)の値をそのまま、該当する文字コード
をもつ文字列にする形になります。と、説明してもわかりにくいでしょうから、実際の例をあげてみます。
16961という整数は、&H4241(16進数)という内部表現になっています。&H42は文字コードでいうと"B"
になります。&H41は"A"です。それでもって、8086系のCPUでは実際のメモリ上では上位バイトと下位バイトが
逆に格納されますので、メモリ上では"AB"という文字列と同じになっています。(少しややこしい話ですが)
よって MKI$(16961)は"AB"という文字列に変換されます。
これは、数値を文字列に変換するSTR$とはまったく変換の仕方が異なりますので注意して下さい。
STR$(16961)は"16961"になります。

MKI$と同じことをあえてやろうとすると、
10 A$=HEX$(16961)
20 L$=LEFT$(A$,2): R$=RIGHT$(A$,2)
30 B$=CHR$(VAL("&H"+R$))+CHR$(VAL("&H"+L$))
とするとB$に"AB"が得られます。不毛ですね。

(5) CVI, CVS, CVD
MKI$,MKS$,MKD$の各関数を用いてランダムファイルに文字列型に変換されて書き込まれたデータを、
数値データに戻す場合に使用します。プログラムの240行でCVIを使用しています。
CVI 2バイト文字列を整数値に変換 MKI$の逆
CVS 4バイト文字列を単精度実数値に変換 MKS$の逆
CVD 8バイト文字列を倍精度実数値に変換 MKD$の逆


(6) おわりに

ここで掲載したサンプルプログラムは、こんなに小さいプログラムなので、データベースとして活用するには
まだまだ機能不足ですが、ランダムファイルの取り扱いの骨格はこんなもんです。いろいろ工夫して使える
プログラムにして下さい。


TOP PAGE >Basic活用研究目次