ども、Norimakiです。
アプリケーションでは、通常、データを作ったりしますが、
そのデータはアプリケーション内ではレコードの構造体で保持する
という場合が僕の場合は多いです。
例えば、所有しているブログとかメールアドレスとかのデータなどですね。
そういう場合は大体、データは1つではなく
複数のデータからなるということになるのですが、
その場合は、TListを使用してデータ群を溜めておきます。
最近のDelphiにはジェネリクスという便利な機能が備わったみたいで、
TList<String>
とか
TList<TNYUserDataRec>
のように(TNYUserDataRecは独自のレコード構造体です)
リスト化して保持することができます。
場合によっては、TDictionaryを使ったりします。
で、あとはファイルなどにデータを保存したり、
ファイルからデータを読み出したりできれば、
アプリケーションのデータI/O処理機能としては、
まぁ満足されるかなと。
ということで、
レコードリストをストリームに保存して、
そのストリームからファイルに保存すれば良いだろうと。
とまぁ、考えられます。
大体の流れとしては、まず、2つのストリームを使います。
データ全体を保存するためのストリーム(1)と、
1つのデータ(レコード)を扱うためのストリーム(2)です。
手順としては、1つのレコードをフィールドごとにストリーム(2)に保存します。
で、保存したストリーム(2)をストリーム(1)に保存(追記)します。
それをレコードの数だけ繰り返すという単純なもの。
ただ、いくつレコードがあるのかという情報も保存しておかなければいけないので、
その情報を、レコードデータを保存するよりも前に保存しておきます。
この際、レコードデータのカウントもレコードデータと同様に、
ストリーム(2)で保存しておきます。
カウンタだけなら、
Stream.Write(Count,SizeOf(Count));
というような記述でOKなんですが、そうではなくて、
一旦、このレコードカウンタもストリーム(2)に保存して、
その後、全体のストリーム(1)に保存するという方法を採用します。
実際、ファイルに保存したい内容ってのは、
レコードのカウンタだけではないですし、
後々、増える可能性もあるわけです。
となると、
レコードデータ以外のデータも1つのブロックとして、
可変サイズに対応しておくべきかなと。そう思うわけです。
レコードデータ以外の保存すべきデータが増減しても、
とりあえず、ブロック部分(ヘッダーブロック)を読んでおけば、
その後のレコードデータの読み込みには影響を与えませんので。
大体がファイルバージョンで管理するんでしょうけど、
処理対象部分を、ストリーム全体(1)とするか、
ストリームのブロック部分(2)にするかで、
対処の面倒くささってのが変わってくるのかなと。
そう思ったので、分けてみた次第です。
で、ストリームの読み書きに関する参考文献がこちら。
>>ストリームにデータを読み書きする
クラスヘルパーとoverloadを使って、ストリームに対する処理を
ちょっとだけ楽にしてくれています。
ということで、こんな感じで使えればと。
//【サンプルレコードデータ】 TNYFirstItemRec=Packed record Prod :string; Title :string; Body :string; Author:string; Seller:string; procedure init; procedure ClearStrings; end; procedure TNYFirstItemRec.init; ClearStrings; end; procedure TNYFirstItemRec.ClearStrings; begin Prod :=''; Title :=''; Body :=''; Author:=''; Seller:=''; end; //【書き込み】 procedure TForm1.WriteData; var MS1,MS2:TMemoryStream; i,Cnt:integer; begin MS1:=TMemoryStream.Create; MS2:=TMemoryStream.Create; try //------------------------- // ヘッダーブロック書き込み //------------------------- Cnt:=XXXXXX.Count; // XXXXXX <- TListのインスタンス MS2.WriteUserData(Cnt); // カウンタ保存 MS1.WriteUserData(MS2); // 全体ストリームに追記 //------------------------- // レコードデータ書き込み //------------------------- for i:=0 to Cnt-1 do begin //MS2初期化 MS2.Clear; //フィールド書き込み MS2.WriteUserData(XXXXXX[i].Prod); MS2.WriteUserData(XXXXXX[i].Title); MS2.WriteUserData(XXXXXX[i].Body); MS2.WriteUserData(XXXXXX[i].Author); MS2.WriteUserData(XXXXXX[i].Seller); //全体ストリーム(MS1)にブロックストリーム(MS2)を追記 MS1.WriteUserData(MS2); end; //end of for i:= //ファイルに保存 MS1.SaveToFile(FILENAME); // FILENAME <- ファイル名 finally FreeAndNil(MS1); FreeAndNil(MS2); end; end; //【読み込み】 procedure TForm1.ReadData; var MS1,MS2:TMemoryStream; i,LoopCnt:integer; Rt:TNYFirstItemRec; begin MS1:=TMemoryStream.Create; MS2:=TMemoryStream.Create; XXXXXX.Clear; // XXXXXX <- TListのインスタンス try MS1.LoadFromFile(FILENAME); // FILENAME <- ファイル名 //------------------------- // ヘッダーブロック読み込み //------------------------- MS1.ReadUserData(MS2); // ヘッダーブロックストリーム読み込み MS2.ReadUserData(LoopCnt); // 記事カウンタ取得 //------------------------- // レコードデータ読み込み //------------------------- for i:=0 to LoopCnt-1 do begin MS1.ReadUserData(MS2); // ブロックストリーム読み込み Rt.init; MS2.ReadUserData(Rt.Prod); MS2.ReadUserData(Rt.Title); MS2.ReadUserData(Rt.Body); MS2.ReadUserData(Rt.Author); MS2.ReadUserData(Rt.Seller); XXXXXX.Add(Rt); // XXXXXX <- TListのインスタンス end; finally FreeAndNil(MS1); FreeAndNil(MS2); end; end;
こんな感じで。
サンプルレコード(TNYFirstItemRec)には、
initとClearStringというプロシージャがありますが、
これはレコード初期化のためのプロシージャです。
initとClearStringに分けているのは気分です。
TVirtualTreeViewでノードを解放するときにレコードのフィールドに文字列がある場合,
文字列は空文字にしてから開放しないとメモリリークを起こすんですが、その感覚です。
そんな感じでしょうか。
ではでは。
Norimakiでした。