レコードリストをストリームに保存する

ども、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でした。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする