Delphi Programming / Object Pascal
ロゴマーク
[掲載 2012年07月13日] [更新 2019年11月09日] Delphi サンプルプログラム集
080_TList へ数値等を格納
動作確認等 Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32
080_TListValue.zip [17,712 KB] 2019年11月09日版 (EXE 同梱)







TList へ数値等を格納


TList は汎用のリスト操作用のクラスです.
Add メソッドを使用して各種のオブジェクトを格納できますが,Add メソッドの引数は Pointer 型です.したがって,数値や文字列を格納するにはポインタ型を使用します.ポインタ型の変数を使用する場合は,その変数用のメモリの確保と解放処理が必要です.

TList の要素にはポインタを代入するので,レコード型のデータを格納する場合はポインタ型の定義が必要です.Windows API で使用するほとんどのレコード型にはポインタ型も定義されています.



Delphi 2009 で実装されたジェネリックス TList<T> を使用すると,ポインタを意識しないてリストの操作が可能です.リストの要素 T にはいろいろな型を指定することができます.
TList<T> には,ソート等のメソッドも実装されています.


[備考 1]
参考リンクの RTTI (実行時型情報) のサンプルは,ジェネリックス Array<T> を使用して,クラスやレコードを配列として扱っています.


[備考 2]
文字列には TStringList という専用のリスト用のクラスがあります.ファイルへの保存,ファイルからの読み込みのメソッドも実装されています.


[備考 3]
リストは文字通り,データのリストの操作用です.リスト項目の入れ換えや追加,挿入,削除,あるいはソート等,データをリストとして管理する目的で使用されています.
配列は,主に要素間の演算が目的で,数値計算 (数学) 言えばベクトルや行列等を扱う場合に使用されています.基本的に,要素の追加,挿入,削除,入れ換え等の機能はありませんが,TArray<T> にはソートの機能が実装されています.
目的にもよりますが,1 次元の配列をリストとして利用することも考えられます.数値型の 1 次元の動的配列であれば,System.Types には TIntegerDynArray, TDoubleDynArray 等の配列型が宣言されています.





01_TList の要素の値はメモリ上のデータの先頭アドレス


TList の要素には,データが書き込まれるメモリの先頭アドレスの値がポインタとして格納されます.本サンプルはそのことを確認するためのテストコードです.

TList.Add メソッドの引数は Pointer 型です.これはアドレス情報だけを持つポインタ型です.このサンプルでは,TList の要素に配列のデータを格納していますが,代入する時に,配列要素の値に @ 演算子 (アドレス演算子) を付けています.@ 演算子を付けると,その変数の値が書き込まれているポインタとしてのメモリアドレスとなります.
つまり,TList の要素には,配列要素のデータが書き込まれているメモリアドレスの値を格納していることになります.

このサンプルでは,TList も配列もデータは同じメモリアドレスにあるわけですから,配列要素の値を変更すると,TList から取得する値も変わります.その逆も同じです.このことを確認するためのコードも追加しています.
TList に格納されているのがアドレスですから,TList からデータを取得する時は,データを格納した時の変数型のポインタ型でキャストして逆参照します.逆参照は,メモリ上のデータを参照するポインタ型の機能です.

本サンプルのような使用方法では,データを格納するメモリとして配列要素のメモリを共有します.ポインタ型の変数を用意してメモリを確保する必要はありません.



図1
TList に配列要素の値を代入

リスト1
TList の要素には,データが書き込まれるメモリアドレスの値が格納される
//=====================================================================================
//  TList の要素の値はデータが書き込まれるメモリアドレスの値
//  その確認用のテスト
//=====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LArray  : array [0..4] of Double;
  LList   : TList;
  LValue  : Double;
  LIndex  : Integer;
begin
  Memo1.Lines.Clear;

  // テスト用のデータを配列要素に代入
  LArray[0] := 100.0;
  LArray[1] := 200.0;
  LArray[2] := 300.0;
  LArray[3] := 400.0;
  LArray[4] := 500.0;

  LList := TList.Create;
  try
    // 配列要素のデータのアドレスを TList に格納
    for LIndex := 0 to Length(LArray) - 1 do begin
      LList.Add(@LArray[LIndex]);
    end;

    // 配列要素の値を変更してみる
    // 対応する TList の要素の値も変わる
    LArray[0] := 5000000.5;

    // TList に格納したデータを表示
    // 実際には TList に格納されているのはデータがあるメモリアドレスの値
    // しだがって,逆参照で取得することになる
    for LIndex := 0 to LList.Count - 1 do begin
      LValue := PDouble(LLIst[LIndex])^;
      Memo1.Lines.Add(FloatToStr(LValue));
    end;

    Memo1.Lines.Add('');

    // TList の要素のデータを変更してみる
    // 対応する配列要素の値も変わる
    LValue  := 3000000.5;
    PDouble(LList[1])^ := LValue;
    Memo1.Lines.Add(FloatToStr(LArray[1]));
  finally
    FreeAndNil(LList);
  end;
end;




02_TList に Integer 型の数値を格納 - 補足 : IndexOf メソッド


TList に Integer 型の整数値を格納する例です.
Integer 型のポインタ型は PInteger 型です.数値型のポインタ型はプリフィックスとして大文字の P を付けた型名になっています.Delphi ではポインタ型のプリフィックスとして慣習的に大文字の P が使用されています.


図2
TList に Integer 型の数値を格納

リスト2
TList に Integer 型の数値を格納
procedure TForm1.Button1Click(Sender: TObject);
var
  LList   : TList;
  LValue  : Integer;
  LpValue : PInteger;
  LIndex  : Integer;
begin
  Memo1.Lines.Clear;

  LList := TList.Create;
  try
    // テスト用のデータを TList に格納
    for LIndex := 0 to 4 do begin
      // メモリ領域を確保
      New(LpValue);
      LValue   := (LIndex + 1) * 100;
      // ポインタの逆参照で値を代入
      // LpValue^ := (LIndex + 1) * 100; でもよい
      LpValue^ := LValue;
      LList.Add(LpValue);
    end;

    // TList に格納した値を表示
    for LIndex := 0 to LList.Count - 1 do begin   
      // ポインタ型の値として取得
      LpValue := PInteger(LLIst[LIndex]);
      // 逆参照して表示  
      Memo1.Lines.Add(IntToStr(LpValue^));
    end;

    // 確保したメモリを解放
    for LIndex := LList.Count - 1 downto 0 do begin
      Dispose(PInteger(LLIst[LIndex]));
    end;
  finally
    FreeAndNil(LList);
  end;
end;


[ 補足 : IndexOf メソッド ]

TList には,指定された値を持つ要素のインデックスを取得する IndexOf メソッドがありますが,このメソッドは,引数のポインタでリストを検索します.つまり,変数のアドレスの値を比較して検索します.Delphi のクラスはポインタですから,コンポーネント等を TList に格納した場合は,そのインスタンス等で検索することができます.
任意の数値の場合,その変数の値が格納されているメモリ上のアドレスの情報は TList には格納されていません.したがって検索できません.整数値や実数値,あるいは文字列の場合は,例えば以下のような関数を作成して対応することも考えられます


//-----------------------------------------------------------------------------
//  TList.IndexOf はポインタ (変数の値が格納されているアドレス) で検索する
//  この関数は引数の TList に格納されている値で検索する
//  引数の AItem には変数のアドレスを渡す (変数に @ 演算子を付ける)
//  ただし Integer 型の数値のリスト専用
//-----------------------------------------------------------------------------
function IndexOf_forInteger(AList: TList; AItem: Pointer): Integer;
var
  LIndex : Integer;
begin
  Result := -1;
  for LIndex := 0 to AList.Count -1 do begin
    if PInteger(AList[LIndex])^ = PInteger(AItem)^ then begin
      Result := LIndex;
      Break;
    end;
  end;
end;




03_TList の要素への値の代入と値の取得


リストの要素に数値を代入したり,数値を取得するには,以下のように,要素に格納している数値型のポインタ型でキャストして逆参照の機能を使用します.
上のコードのように,ポインタ型の変数を使用することもできます.その場合,代入先のポインタ型の変数のメモリを確保する必要はありません.

    // リストの要素に値を代入する
    // LValue は Integer 型の変数
    // リストに格納されているのが Integer 型のデータの場合は
    //  そのポインタ型の PInteger でキャストして逆参照する
    LValue := 500000;
    PInteger(LList[1])^ := LValue;

    // リストの要素の値を逆参照で取得する
    LValue := PInteger(LList[1])^;




04_New 関数によるメモリの確保と AllocMem 関数


New 関数は,ポインタ型の変数を使用してデータを書き込むメモリ領域を確保します.New 関数を実行してもポインタ型の変数自身のアドレスは変わりません.ポインタ型の変数のアドレスに書き込まれる値が変わります.その値がホインタとしてのアドレスの値です.そのアドレスから,データの型に応じたバイト数のメモリが確保されます.

下図はそのことを確認した結果です.左側の値がポインタ型の変数自身の値です.その右側は,New 関数を実行した結果,そのアドレスに書き込まれたメモリの値です.この値が TList に格納された値です.右端の値はその値を,データが書き込まれているアドレスとして取得したデータです.TList に格納した値と一致しています.
ポインタ型の変数が,ポインタとしての役割をしていることが確認できます.



図3
ポインタ型の変数自身のアドレスと,そのアドレスのメモリの値

procedure TForm1.Button1Click(Sender: TObject);
var
  LList     : TList;
  LpValue   : PInteger;
  LAddress  : NativeInt;
  LAdrData  : NativeInt;
  LDigits   : Integer;
  LDstValue : Integer;
  LIndex    : Integer;
begin
  Memo1.Lines.Clear;

  LDigits := SizeOf(Pointer) * 2;

  LList := TList.Create;
  try
    for LIndex := 0 to 4 do begin
      New(LpValue);

      LpValue ^ := (LIndex + 1) * 100;
      LList.Add(LpValue);

      LAddress  := NativeInt(@LpValue);
      LAdrData  := PNativeInt(LAddress)^;
      LDstValue := PInteger(LAdrData)^;

      Memo1.Lines.Add('$' + IntToHex(LAddress, LDigits)
                 + '   $' + IntToHex(LAdrData, LDigits)
                 + '   ' + IntToStr(LDstValue));
    end;

    for LIndex := LList.Count - 1 downto 0 do begin
      Dispose(PInteger(LLIst[LIndex]));
    end;
  finally
    FreeAndNil(LList);
  end;
end;


New 関数は確保したメモリを初期化はしません.AllocMem 関数を使用すると,確保したメモリを初期化できます.AllocMem 関数で確保したメモリは,FreeMem 関数を使用して解放します.



図4
New 関数で確保したメモリの値
図5
AllocMem 関数で確保したメモリの値

//=====================================================================================
//  New 関数によるメモリの確保
//  確保したメモリの初期化は行わない
//=====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LList     : TList;
  LpValue   : PInteger;
  LAddress  : NativeInt;
  LAdrData  : NativeInt;
  LDigits   : Integer;
  LDstValue : Integer;
  LIndex    : Integer;
begin
  Memo1.Lines.Clear;

  LDigits := SizeOf(Pointer) * 2;

  LList := TList.Create;
  try
    for LIndex := 0 to 4 do begin
      New(LpValue);

      LList.Add(LpValue);

      LAddress  := NativeInt(@LpValue);
      LAdrData  := PNativeInt(LAddress)^;
      LDstValue := PInteger(LAdrData)^;

      Memo1.Lines.Add('$' + IntToHex(LAddress, LDigits)
                 + '   $' + IntToHex(LAdrData, LDigits)
                 + '   ' + IntToStr(LDstValue));
    end;

    for LIndex := LList.Count - 1 downto 0 do begin
      Dispose(PInteger(LLIst[LIndex]));
    end;
  finally
    FreeAndNil(LList);
  end;
end;

//=====================================================================================
//  AllocMem 関数によるメモリの確保
//  確保したメモリの初期化を行う
//=====================================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LList     : TList;
  LpValue   : PInteger;
  LAddress  : NativeInt;
  LAdrData  : NativeInt;
  LDigits   : Integer;
  LDstValue : Integer;
  LIndex    : Integer;
begin
  Memo1.Lines.Clear;

  LDigits := SizeOf(Pointer) * 2;

  LList := TList.Create;
  try
    for LIndex := 0 to 4 do begin
      LpValue := AllocMem(SizeOf(Integer));

      LList.Add(LpValue);

      LAddress  := NativeInt(@LpValue);
      LAdrData  := PNativeInt(LAddress)^;
      LDstValue := PInteger(LAdrData)^;

      Memo1.Lines.Add('$' + IntToHex(LAddress, LDigits)
                 + '   $' + IntToHex(LAdrData, LDigits)
                 + '   ' + IntToStr(LDstValue));
    end;

    for LIndex := LList.Count - 1 downto 0 do begin
      FreeMem(PInteger(LLIst[LIndex]));
    end;
  finally
    FreeAndNil(LList);
  end;
end;




05_TList に Double 型の数値を格納


TList に Double 型の実数値を格納する例です.Double 型のポインタ型は PDouble 型です.


図6
TList に Double 型の数値を格納

リスト3
TList に Double 型の数値を格納
procedure TForm1.Button1Click(Sender: TObject);
var
  LList   : TList;
  LValue  : Double;
  LpValue : PDouble;
  LIndex  : Integer;
begin
  Memo1.Lines.Clear;

  LList := TList.Create;
  try
    // テスト用のデータを TList に格納
    for LIndex := 0 to 4 do begin
      // メモリ領域を確保
      New(LpValue);
      // ポインタの逆参照で値を代入
      // LpValue^ := (LIndex + 1) * 100.0 + 0.5; でもよい
      LValue   := (LIndex + 1) * 100.0 + 0.5;
      LpValue^ := LValue;
      LList.Add(LpValue);
    end;

    // TList に格納した値を表示
    for LIndex := 0 to LList.Count - 1 do begin
      // ポインタ型の値として取得
      LpValue := PDouble(LLIst[LIndex]);
      // 逆参照して表示  
      Memo1.Lines.Add(FloatToStr(LpValue^));
    end;

    // 確保したメモリを解放
    for LIndex := LList.Count - 1 downto 0 do begin
      Dispose(PDouble(LLIst[LIndex]));
    end;
  finally
    FreeAndNil(LList);
  end;
end;




06_TList に String 型の文字列を格納


TList に String 型の文字列を格納する例です.String 型のポインタ型は PString 型です.
String 型には,String 型専用の TStringList と言うクラスがあります.


図7
TList に String 型の文字列を格納

リスト4
TList に String 型の文字列を格納
procedure TForm1.Button1Click(Sender: TObject);
var
  LList   : TList;
  LValue  : string;
  LpValue : PString;
  LIndex  : Integer;
begin
  Memo1.Lines.Clear;

  LList := TList.Create;
  try
    // テスト用のデータを TList に格納
    for LIndex := 0 to 4 do begin
      // メモリ領域を確保
      New(LpValue);
      LValue   := '文字列 : ' + IntToStr((LIndex + 1) * 100);
      // ポインタの逆参照で値を代入
      LpValue^ := LValue;
      LList.Add(LpValue);
    end;

    // TList に格納した値を表示
    for LIndex := 0 to LList.Count - 1 do begin
      // ポインタ型の値として取得
      LpValue := PString(LLIst[LIndex]);
      Memo1.Lines.Add(LpValue^);
    end;

    // 確保したメモリを解放
    for LIndex := LList.Count - 1 downto 0 do begin
      Dispose(PString(LLIst[LIndex]));
    end;
  finally
    FreeAndNil(LList);
  end;
end;




07_TList にレコード型のデータを格納


TList にレコード型のデータを格納する例です.テスト用に自前のレコード型を定義して,そのポインタ型も定義しています.レコード型のポインタ型は,レコードのデータを取得したり代入する時に必要です.
Windows API で使用するレコード型については,そのポインタ型も定義されています.


図8
TList にレコード型のデータを格納

リスト5
TList にレコード型のデータを格納
implementation

uses
  System.StrUtils;

{$R *.dfm}

type
  TDataRec = record
    Text  : string;
    Value : Integer;
  end;
  PDataRec = ^TDataRec;

var
  FRecordList : TList;

//=====================================================================================
//  フォーム生成時の処理
//=====================================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  FRecordList := TList.Create;
end;

//=====================================================================================
//  フォーム破棄時の処理
//=====================================================================================
procedure TForm1.FormDestroy(Sender: TObject);
var
  LIndex : Integer;
begin
  // 確保したメモリを解放
  for LIndex := FRecordList.Count - 1 downto 0 do begin
    Dispose(PDataRec(FRecordList[LIndex]));
  end;
  FRecordList.Clear;
  if Sender = nil then Exit;

  FreeAndNil(FRecordList);
end;

//=====================================================================================
//  TList にレコード型のデータを格納
//  uses に System.StrUtils が必要
//=====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LpDataRec : PDataRec;
  LIndex    : Integer;
begin
  Memo1.Lines.Clear;

  FormDestroy(nil);

  // テスト用のデータを TList に格納
  for LIndex := 0 to 4 do begin
    // メモリ領域を確保
    New(LpDataRec);

    // レコード型の初期化
    // 初期化を忘れると正しいデータとして格納できないことがある
    LpDataRec^ := System.Default(TDataRec);

    // レコード型のメンバに値をセット
    LpDataRec^.Text  := DupeString('字', LIndex + 1);
    LpDataRec^.Value := (LIndex + 1) * 100;

    // ポインタをリストに追加
    FRecordList.Add(LpDataRec);
  end;
end;

//=====================================================================================
//  TList に格納しているレコード型のデータを表示
//=====================================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LpDataRec : PDataRec;
  LText     : string;
  LIndex    : Integer;
begin
  Memo1.Lines.Clear;

  // TList に格納しているデータを表示
  for LIndex := 0 to FRecordList.Count - 1 do begin
    // ポインタ型の値として取得
    LpDataRec := PDataRec(FRecordList[LIndex]);
    LText := IntToStr(LpDataRec^.Value) + '  ' + string(LpDataRec^.Text);
    Memo1.Lines.Add(LText);
  end;
end;




08_TList のソート


TList のデータをソートする例です.以下のコードでは Integer 型の数値を降順にソートします.


図9
TList に格納した Integer 型の数値を降順ソート

リスト6
TList に格納した Integer 型の数値を降順ソート
//--------------------------------------------------------------------------------------
//  TList のソート関数のコールバック関数
//  Integer 型の場合 (降順)
//--------------------------------------------------------------------------------------
function CompareFunc(Item1, Item2: Pointer): Integer;
begin
  Result := PInteger(Item2)^ - PInteger(Item1)^;
end;

//=====================================================================================
//  TList に整数値を格納してソート
//=====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LList   : TList;
  LValue  : Integer;
  LpValue : PInteger;
  LIndex  : Integer;
begin
  Memo1.Lines.Clear;

  LList := TList.Create;
  try
    // テスト用のデータを TList に格納
    for LIndex := 0 to 4 do begin
      New(LpValue);
      LValue   := (LIndex + 1) * 100;
      LpValue^ := LValue;
      LList.Add(LpValue);
    end;

    //------------------------
    // ソート
    // 関数名は任意
    //------------------------
    LList.Sort(CompareFunc);

    // ソートの結果として TList に格納している値を表示
    for LIndex := 0 to LList.Count - 1 do begin
      LpValue := PInteger(LLIst[LIndex]);
      Memo1.Lines.Add(IntToStr(LpValue^));
    end;

    // 確保したメモリを解放
    for LIndex := LList.Count - 1 downto 0 do begin
      Dispose(PInteger(LLIst[LIndex]));
    end;
  finally
    FreeAndNil(LList);
  end;
end;


TList に Double 型の数値が格納されている場合は,ソート用の関数を,例えば次のようにすると降順ソートできます.

//--------------------------------------------------------------------------------------
//  TList のソート関数のコールバック関数
//  Double 型の場合 (降順)
//  uses に System.Math が必要
//--------------------------------------------------------------------------------------
function CompareFunc(Item1, Item2: Pointer): Integer;
begin
  Result := Sign(PDouble(Item2)^ - PDouble(Item1)^);
end;


Math ユニットに実装されている CompareValue 関数を利用すると,ソート用の関数を以下のように書くことができます.

//--------------------------------------------------------------------------------------
//  TList のソート関数のコールバック関数
//  Double 型の場合 (降順)
//  uses に System.Math が必要
//--------------------------------------------------------------------------------------
function CompareFunc(Item1, Item2: Pointer): Integer;
begin
  Result :=  CompareValue(PDouble(Item2)^, PDouble(Item1)^);
end;


レコード型のソートは,レコードのメンバの値で処理します.前出のレコード型のサンプルの場合は,例えば次のようになります.

  //--------------------------------------------------------------------------------------
//  TList のソート関数のコールバック関数
//  レコード型の場合 (Value メンバの値で降順)
//--------------------------------------------------------------------------------------
function CompareFunc(Item1, Item2: Pointer): Integer;
begin
  Result := PDataRec(Item2)^.Value - PDataRec(Item1)^.Value;
end;




09_TList のソート - 無名メソッド


TList のソート関数には TList.Sort メソッドの他に TList.SortList というメソッドがあります.このメソッドを使用すると,以下のように無名メソッド (無名関数) が使用できます.
TList.SortList は Delphi 2010 で追加されたメソッドです.


    LList.SortList(
      function (Item1, Item2: Pointer): Integer
      begin
        Result := PInteger(Item2)^ - PInteger(Item1)^;
      end
     );


var
  LSortFunc : TListSortCompareFunc;
begin
    //;
    LSortFunc := function (Item1, Item2: Pointer): Integer
    begin
      Result := PInteger(Item2)^ - PInteger(Item1)^;
    end;
    LList.SortList(LSortFunc);
    //




10_TList のデータをファイルに保存


TList には,データをファイルに保存するメソッド類はありません.本サンプルでは TList のデータをメモリストリームに書き込んで,そのメモリストリームをファイルに保存します.
ファイルから読み込む時も,ファイルに保存されているデータをメモリストリームにロードします.そのメモリストリームからデータを読み出して TList の要素に代入します.

ファイルからデータ読み出す時に読み出すデータのサイズが必要です.したがって,ファイルへの保存と読み出しは,TList に格納されているデータが固定長の場合に限ります.String 型等の可変長のデータは以下のコードでは処理できません.



図10
ファイルに保存したデータを読み込んで表示

リスト7
TList のデータのファイルへの保存とファイルからの読み込み
//=====================================================================================
//  TList に実数値を格納してファイルに保存
//=====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LList     : TList;
  LValue    : Double;
  LpValue   : PDouble;
  LStream   : TMemoryStream;
  LFilePath : string;
  LIndex    : Integer;
begin
  LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin';

  Memo1.Lines.Clear;

  LList := TList.Create;
  try
    // テスト用のデータを TList に格納
    for LIndex := 0 to 4 do begin
      New(LpValue);
      LpValue^  := (LIndex + 1) * 100.0 + 0.5;
      LList.Add(LpValue);
    end;

    // TList に格納したデータをメモリストリームに書き込む
    LStream := TMemoryStream.Create;
    try
      for LIndex := 0 to LList.Count - 1 do begin
        LStream.Write(LLIst[LIndex], SizeOf(Double));
      end;
      // メモリストリームをファイルに保存
      LStream.SaveToFile(LFilePath);
    finally
      FreeAndNil(LStream);
    end;

    // 確保したメモリを解放
    for LIndex := LList.Count - 1 downto 0 do begin
      Dispose(PDouble(LLIst[LIndex]));
    end;
  finally
    FreeAndNil(LList);
  end;
end;

//=====================================================================================
//  ファイルからデータを読み込んで TList に格納
//=====================================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LList     : TList;
  LValue    : Double;
  LpValue   : PDouble;
  LStream   : TMemoryStream;
  LFilePath : string;
  LIndex    : Integer;
begin
  LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin';
  Memo1.Lines.Clear;

  LList   := TList.Create;
  LStream := TMemoryStream.Create;
  try
    // メモリストリームにファイルの内容をロードする
    LStream.LoadFromFile(LFilePath);

    while True do begin
      // メモリストリームからデータを読み込む
      LStream.ReadData(LValue);

      // 読み込んだデータを TList に追加
      New(LpValue);
      LpValue^ := LValue;
      LList.Add(LpValue);

      if LStream.Position >= LStream.Size then Break;
    end;

    // TList に格納した値を表示
    for LIndex := 0 to LList.Count - 1 do begin
      LpValue := PDouble(LLIst[LIndex]);
      Memo1.Lines.Add(FloatToStr(LpValue^));
    end;

    // 確保したメモリを解放
    for LIndex := LList.Count - 1 downto 0 do begin
      Dispose(PDouble(LLIst[LIndex]));
    end;
  finally
    FreeAndNil(LList);
    FreeAndNil(LStream);
  end;
end;




11_TList のデータをファイルに保存 - ファイル型を使用


ファイル型を使用して TList のデータをファイルに保存し,読み込む時もファイル型を使用して,ファイルからデータを読み出して TList の要素に代入する例です.


リスト8
TList のデータのファイルへの保存とファイルからの読み込み.ファイル型を使用する例
//=====================================================================================
//  TList に実数値を格納してファイルに保存
//  ファイル型を使用する例
//=====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LList     : TList;
  LValue    : Double;
  LpValue   : PDouble;
  LFile     : File of Byte;
  LFilePath : string;
  LIndex    : Integer;
begin
  LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin';

  Memo1.Lines.Clear;

  LList := TList.Create;
  try
    // テスト用のデータを TList に格納
    for LIndex := 0 to 4 do begin
      New(LpValue);
      LpValue^  := (LIndex + 1) * 100.0 + 0.5;
      LList.Add(LpValue);
    end;

    // 保存ファイル名をファイル型の変数に割り当てる
    AssignFile(LFile, LFilePath);
    try
      // 新規にファイルを作成して開く(既存ファイルは上書き)
      Rewrite(LFile);

      // データを順番にファイルに書き込む
      for LIndex := 0 to LList.Count - 1 do begin
        LValue := PDouble(LList[LIndex])^;
        BlockWrite(LFile, LValue, SizeOf(LValue));
      end;
    finally
      // ファイルを閉じる
      CloseFile(LFile);
    end;

    // 確保したメモリを解放
    for LIndex := LList.Count - 1 downto 0 do begin
      Dispose(PDouble(LLIst[LIndex]));
    end;
  finally
    FreeAndNil(LList);
  end;
end;

//=====================================================================================
//  ファイルからデータを読み込んで TList に格納
//  ファイル型を使用する例
//=====================================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LList     : TList;
  LValue    : Double;
  LpValue   : PDouble;
  LFile     : File of Byte;
  LFilePath : string;
  LIndex    : Integer;
begin
  LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin';
  if not FileExists(LFilePath) then Exit;

  Memo1.Lines.Clear;

  LList   := TList.Create;
  try
    // 保存ファイル名をファイル型の変数に割り当てる
    AssignFile(LFile, LFilePath);
    try
      // ファイルを開く
      Reset(LFile);
      // ファイルからデータを読み込んでリストに追加
      while True do begin
        if Eof(LFile) then break;

        BlockRead(LFile, LValue, SizeOf(LValue));
        // メモリを確保してリストに追加
        New(LpValue);
        LpValue^ := LValue;
        LList.Add(LpValue);
      end;
    finally
      // ファイルを閉じる
      CloseFile(LFile);
    end;

    // TList に格納した値を表示
    for LIndex := 0 to LList.Count - 1 do begin
      LpValue := PDouble(LLIst[LIndex]);
      Memo1.Lines.Add(FloatToStr(LpValue^));
    end;

    // 確保したメモリを解放
    for LIndex := LList.Count - 1 downto 0 do begin
      Dispose(PDouble(LLIst[LIndex]));
    end;
  finally
    FreeAndNil(LList);
  end;
end;




12_TList のデータをファイルに保存 - レコード型


TList に格納しているレコード型のデータのファイルへの保存と読み出しのサンプルです.
TList でレコード型を使用する時は,そのポインタ型も定義しておきます.レコード型のポインタ型は,レコードのデータを取得したり代入する時に必要です.Delphi では,Windows API で使用するレコード型については,そのポインタ型も定義されています.

TList に格納されているデータをファイルに保存して読み出す場合,TList に格納されているデータが固定長の場合に限ります.String 型は可変長なので使用できません.このサンプルでは,レコード型の文字列のメンバとして Char 型の静的な文字配列を使用しています.
この Char 型の静的な文字配列は,Windows API の構造体でもよく使用されています.



図11
ファイルに保存したデータを読み込んで表示

リスト9
TList のレコード型データをファイルに保存
implementation

uses
  System.StrUtils;

{$R *.dfm}

type
  TDataRec = record
    Text  : array [0..29] of Char;
    Value : Integer;
  end;
  PDataRec = ^TDataRec;

//=====================================================================================
//  TList にレコード型のデータを格納してファイルに保存
//  uses に System.StrUtils が必要
//=====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LList     : TList;
  LpDataRec : PDataRec;
  LStream   : TMemoryStream;
  LFilePath : string;
  LIndex    : Integer;
begin
  LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin';

  Memo1.Lines.Clear;

  LList := TList.Create;
  try
    // テスト用のデータを TList に格納
    for LIndex := 0 to 4 do begin
      // メモリを確保
      New(LpDataRec);

      // レコード型の初期化
      // 初期化を忘れると正しいデータとして格納できないことがある
      LpDataRec^ := System.Default(TDataRec);
      // string 型の文字列を Char 型の文字配列にコピー
      StrPCopy(LpDataRec^.Text, DupeString('字', LIndex + 1));
      LpDataRec^.Value := (LIndex + 1) * 100;

      // リストに追加
      LList.Add(LpDataRec);
    end;

    // TList に格納したデータをメモリストリームに書き込む
    LStream := TMemoryStream.Create;
    try
      for LIndex := 0 to LList.Count - 1 do begin
        LStream.Write(LLIst[LIndex], SizeOf(LpDataRec^))
      end;
      // メモリストリームをファイルに保存
      LStream.SaveToFile(LFilePath);
    finally
      FreeAndNil(LStream);
    end;

    // 確保したメモリを解放
    for LIndex := LList.Count - 1 downto 0 do begin
      Dispose(PDataRec(LLIst[LIndex]));
    end;
  finally
    FreeAndNil(LList);
  end;
end;

//=====================================================================================
//  ファイルからデータを読み込んで TList に格納
//=====================================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LList     : TList;
  LpDataRec : PDataRec;
  LStream   : TMemoryStream;
  LFilePath : string;
  LText     : string;
  LIndex    : Integer;
begin
  LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin';
  if not FileExists(LFilePath) then Exit;

  Memo1.Lines.Clear;

  LList := TList.Create;
  try
    LStream := TMemoryStream.Create;
    try
      // メモリストリームにファイルの内容をロードする
      LStream.LoadFromFile(LFilePath);

      while True do begin
        // ポインタのメモリを確保
        New(LpDataRec);

        // メモリストリームからデータを読み込む
        LStream.Read(LpDataRec^, SizeOf(TDataRec));
        // 読み込んだデータを TList に格納
        LList.Add(LpDataRec);

        if LStream.Position >= LStream.Size then Break;
      end;
    finally
      FreeAndNil(LStream);
    end;

    // TList に格納した値を表示
    for LIndex := 0 to LList.Count - 1 do begin
      // ポインタ型の値として取得
      LpDataRec := PDataRec(LLIst[LIndex]);
      LText := IntToStr(LpDataRec^.Value) + '  ' + string(LpDataRec^.Text);
      Memo1.Lines.Add(LText);
    end;

    // 確保したメモリを解放
    for LIndex := LList.Count - 1 downto 0 do begin
      Dispose(PDataRec(LLIst[LIndex]));
    end;
  finally
    FreeAndNil(LList);
  end;
end;




13_TList<T> に Integer 型の数値を格納


TList<T> に Integer 型の数値を格納する例です.型の T を Integer にします.


図12
TList<T> に Integer 型の数値を格納

リスト10
TList<T> に Integer 型の数値を格納.uses に Generics.Collections が必要
procedure TForm1.Button1Click(Sender: TObject);
var
  LListInt : TList<Integer>;
  LValue   : Integer;
  LIndex   : Integer;
begin
  Memo1.Lines.Clear;

  LListInt := TList<Integer>.Create;
  try
    // テスト用のデータを格納
    for LIndex := 0 to 4 do begin
      LValue := (LIndex + 1) * 100;
      LListInt.Add(LValue);
    end;

    // 格納したデータを表示
    for LIndex := 0 to LListInt.Count - 1 do begin
      LValue := LListInt[LIndex];
      Memo1.Lines.Add(IntToStr(LValue));
    end;
  finally
    FreeAndNil(LListInt);
  end;
end;




14_TList<T> に Double 型の数値を格納


TList<T> に Double 型の数値を格納する例です.型の T を Double にします.


図13
TList<T> に Double 型の数値を格納

リスト11
TList<T> に Double 型の数値を格納.uses に Generics.Collections が必要
procedure TForm1.Button1Click(Sender: TObject);
var
  LListDouble : TList<Double>;
  LValue      : Double;
  LIndex      : Integer;
begin
  Memo1.Lines.Clear;

  LListDouble := TList<Double>.Create;
  try
    // テスト用のデータを格納
    for LIndex := 0 to 4 do begin
      LValue := (LIndex + 1) * 100.0 + 0.5;
      LListDouble.Add(LValue);
    end;

    // 格納したデータを表示
    for LIndex := 0 to LListDouble.Count - 1 do begin
      LValue := LListDouble[LIndex];
      Memo1.Lines.Add(FloatToStr(LValue));
    end;
  finally
    FreeAndNil(LListDouble);
  end;
end;




15_TList<T> に String 型の文字列を格納


TList<T> に String 型の文字列を格納する例です.型の T を String にします.
String 型には,String 型専用の TStringList と言うクラスがあります.


図14
TList<T> に String 型の文字列を格納

リスト12
TList<T> に String 型の文字列を格納.uses に Generics.Collections が必要
procedure TForm1.Button1Click(Sender: TObject);
var
  LListStr : TList<string>;
  LValue   : string;
  LIndex   : Integer;
begin
  Memo1.Lines.Clear;

  LListStr := TList<String>.Create;
  try
    // テスト用のデータを格納
    for LIndex := 0 to 4 do begin
      LValue := '文字列 : ' + IntToStr((LIndex + 1) * 100);
      LListStr.Add(LValue);
    end;

    // 格納したデータを表示
    for LIndex := 0 to LListStr.Count - 1 do begin
      LValue := LListStr[LIndex];
      Memo1.Lines.Add(LValue);
    end;
  finally
    FreeAndNil(LListStr);
  end;
end;




16_TList<T> にレコード型のデータを格納


TList<T> にレコード型のデータを格納する例です.テスト用に自前のレコード型を定義しています.


図15
TList<T> にレコード型のデータを格納

リスト13
TList<T> にレコード型のデータを格納
implementation

uses
  System.StrUtils;

{$R *.dfm}

type
  TDataRec = record
    Text  : string;
    Value : Integer;
  end;
  PDataRec = ^TDataRec;

var
  FListRec : TList<TDataRec>;

//=====================================================================================
//  フォーム生成時の処理
//=====================================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  FListRec := TList<TDataRec>.Create;
end;

//=====================================================================================
//  フォーム破棄時の処理
//=====================================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FListRec.Clear;
  if Sender = nil then Exit;

  FreeAndNil(FListRec);
end;

//=====================================================================================
//  ジェネリックスの TList にレコード型のデータを代入
//  uses に Generics.Collections, System.StrUtils が必要
//=====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LSrcRec : TDataRec;
  LIndex  : Integer;
begin
  Memo1.Lines.Clear;

  FormDestroy(nil);

  // テスト用のデータを格納
  for LIndex := 0 to 4 do begin
    // レコード型の初期化
    // 初期化を忘れると正しいデータとして格納できないことがある
    LSrcRec := System.Default(TDataRec);

    // レコード型のメンバに値をセット
    LSrcRec.Text  := DupeString('文字列', LIndex + 1);
    LSrcRec.Value := (LIndex + 1) * 100;
    FListRec.Add(LSrcRec);
  end;
end;

//=====================================================================================
//  ジェネリックスの TList に格納しているレコード型のデータを表示
//=====================================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LDstRec : TDataRec;
  LText   : string;
  LIndex  : Integer;
begin
  Memo1.Lines.Clear;

  // 格納しているデータを表示
  for LIndex := 0 to FListRec.Count - 1 do begin
    LDstRec := FListRec[LIndex];
    LText   := IntToStr(LDstRec.Value) + '  ' + string(LDstRec.Text);
    Memo1.Lines.Add(LText);
  end;
end;




17_TList<T> の OnNotify イベントで項目の追加や削除を検出


TList<T> には,項目を追加,削除,あるいは抽出した時に発生する OnNotify イベントが実装されています.そのイベントの使用例です.


図16
TList<T> の項目の追加や削除のイベント

リスト14
TList<T> の OnNotify イベントで項目の追加や削除を検出
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, 
  Generics.Collections;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    // イベントの手続き宣言文
    // Item は使用する型に応じて変更する
    procedure MyNotify(Sender: TObject; const Item: Integer;
      Action: TCollectionNotification);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=====================================================================================
//  ジェネリックスの TList のイベント OnNotify の使用例
//  uses に Generics.Collections が必要
//=====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LListInt : TList<Integer>;
  LValue   : Integer;
  LIndex   : Integer;
begin
  Memo1.Lines.Clear;

  LListInt := TList<Integer>.Create;
  try
    LListInt.OnNotify := MyNotify;

    // テスト用のデータを格納
    for LIndex := 0 to 4 do begin
      LValue := (LIndex + 1) * 100;
      LListInt.Add(LValue);
    end;
  finally
    FreeAndNil(LListInt);
  end;
end;

//------------------------------------------------------------------------------------^^^
//  イベントの実現部
//------------------------------------------------------------------------------------^^^
procedure TForm1.MyNotify(Sender: TObject; const Item: Integer;
  Action: TCollectionNotification);
begin
  case Action of
    cnAdded:
      Memo1.Lines.Add('追加: '+ IntToStr(Item));
    cnRemoved:
      Memo1.Lines.Add('削除: ' + IntToStr(Item));
    cnExtracted:
      Memo1.Lines.Add('抽出: ' + IntToStr(Item));
  end;
end;

end.




18_TList<T> のソート


TList<T> のデータをソートする例です.Integer 型の数値を降順にソートします.



図17
TList<T> に格納した Integer 型の数値を降順ソート

リスト15
TList<T> に格納した Integer 型の数値を降順ソート
implementation

uses
  Generics.Defaults;

{$R *.dfm}

//-----------------------------------------------------------------------------
//  ジェネリックス TList のソート関数
//  Integer 型用なので引数の型を Integer にしている
//-----------------------------------------------------------------------------
function CompareFunc(const Left, Right: Integer): Integer;
begin
  // これは降順
  Result := Right - Left;
end;

//=====================================================================================
//  ジェネリックスの TList に整数型の値を代入してソート
//  uses に Generics.Collections, Generics.Defaults が必要
//=====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LListInt : TList<Integer>;
  LValue   : Integer;
  LIndex   : Integer;
begin
  Memo1.Lines.Clear;

  LListInt := TList<Integer>.Create;
  try
    // テスト用のデータを格納
    for LIndex := 0 to 4 do begin
      LValue := (LIndex + 1) * 100;
      LListInt.Add(LValue);
    end;

   //---------------------------------------------------------
    //  ソート
    //  Construct の引数の関数名は任意
    //  数値,文字列等の昇順ソートであれば引数なしでよい
    //  TComparer関係の使用にはusesにGenerics.Defaultsが必要
    //---------------------------------------------------------
    LListInt.Sort(TComparer<Integer>.Construct(CompareFunc));

    // ソートの結果として格納しているデータを表示
    for LIndex := 0 to LListInt.Count - 1 do begin
      LValue := LListInt[LIndex];
      Memo1.Lines.Add(IntToStr(LValue));
    end;
  finally
    FreeAndNil(LListInt);
  end;
end;


Double 型の場合は,例えば以下のように処理できます.


リスト16
TList<T> に格納した Double 型の数値を降順ソート
implementation

uses
  Generics.Defaults,
  System.Math;

{$R *.dfm}  

//-----------------------------------------------------------------------------
//  ジェネリックス TList のソート関数 
//  Double 型用なので引数の型を Double にしている
//  uses に System.Math が必要
//-----------------------------------------------------------------------------
function CompareFunc(const Left, Right: Double): Integer;
begin
  // これは降順の場合
  // 昇順の場合は引数を逆にする
  Result := Sign(Right - Left);
end;

//=====================================================================================
//  ジェネリックスの TList に実数型の値を代入してソート
//  uses に Generics.Collections, Generics.Defaults が必要
//=====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LListDouble : TList<Double>;
  LValue      : Double;
  LIndex      : Integer;
begin
  Memo1.Lines.Clear;

  LListDouble := TList<Double>.Create;
  try
    // テスト用のデータを格納
    for LIndex := 0 to 4 do begin
      LValue := (LIndex + 1) * 100.0 + 0.5;
      LListDouble.Add(LValue);
    end;

   //---------------------------------------------------------
    //  ソート
    //  Construct の引数の関数名は任意
    //  数値,文字列等の昇順ソートであれば引数なしでよい        
    //  TComparer関係の使用にはusesにGenerics.Defaultsが必要
    //---------------------------------------------------------
    LListDouble.Sort(TComparer<Double>.Construct(CompareFunc));

    // ソートの結果として格納しているデータを表示
    for LIndex := 0 to LListDouble.Count - 1 do begin
      LValue := LListDouble[LIndex];
      Memo1.Lines.Add(FloatToStr(LValue));
    end;
  finally
    FreeAndNil(LListDouble);
  end;
end;


Math ユニットに実装されている CompareValue 関数を利用すると,ソート用の関数を以下のように書くことができます.

//-----------------------------------------------------------------------------
//  ジェネリックス TList のソート関数 
//  Double 型用なので引数の型を Double にしている
//  uses に System.Math が必要
//-----------------------------------------------------------------------------
function CompareFunc(const Left, Right: Double): Integer;
begin
  // これは降順の場合
  Result := CompareValue(Right, Left);
end;


レコード型のソートは,レコードのメンバの値で処理します.前出のレコード型のサンプルの場合は,例えば次のようになります.

//-----------------------------------------------------------------------------
//  ジェネリックス TList のソート関数
//  レコード型で,レコード型の型名が TDataRec の場合
//  レコード型のメンバの値で処理する
//-----------------------------------------------------------------------------
function CompareFunc(const Left, Right: TDataRec): Integer;
begin
  // これは降順
  Result := Right.Value - Left.Value;
end;


   // ソートの実行
   LListRec.Sort(TComparer<TDataRec>.Construct(CompareFunc));
// 




19_TList<T> のソート - 無名メソッド


TList<T>.Sort メソッドでは無名メソッドが使用できます.以下のコードは Integer 型の数値を降順にソートする例です.


   //---------------------------------------------------------
    //  ソート
    //  数値,文字列等の昇順ソートであれば引数なしでよい
    //  TComparer関係の使用にはusesにGenerics.Defaultsが必要
    //---------------------------------------------------------
    LListInt.Sort(TComparer<Integer>.Construct(
      function(const Left, Right: Integer): Integer
      begin
        // これは降順の判定
        Result := Right -  Left;
      end)
    );


var
  LSortFunc : TComparison<Integer>;
begin

   //---------------------------------------------------------
    //  ソート
    //  数値,文字列等の昇順ソートであれば引数なしでよい
    //  TComparer関係の使用にはusesにGenerics.Defaultsが必要
    //---------------------------------------------------------
    LSortFunc := function(const Left, Right: Integer): Integer
    begin
      // これは降順の判定
      Result := Right -  Left;
    end;
    LListInt.Sort(TComparer<Integer>.Construct(LSortFunc));
    //


[備考]
以下の記事にレコード型のソートの例があります.





20_TList<T> のデータをファイルに保存


TList<T> には,データをファイルに保存するメソッド類はありません.本サンプルでは TList<T> のデータをメモリストリームに書き込んで,そのメモリストリームをファイルに保存します.
ファイルから読み込む時も,ファイルに保存されているデータをメモリストリームにロードします.そのメモリストリームからデータを読み出して TList<T> の要素に代入します.

ファイルからデータ読み出す時に読み出すデータのサイズが必要です.したがって,ファイルへの保存と読み出しは,TList<T> に格納されているデータが固定長の場合に限ります.String 型等の可変長のデータは以下のコードでは処理できません.



図18
ファイルに保存したデータを読み込んで表示

リスト17
TList<T> のデータのファイルへの保存とファイルからの読み込み
//=====================================================================================
//  ジェネリックスの TList に実数値を格納してファイルに保存
//  uses に Generics.Collections が必要
//=====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LListDouble : TList<Double>;
  LValue      : Double;
  LStream     : TMemoryStream;
  LFilePath   : string;
  LIndex      : Integer;
begin
  LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin';

  Memo1.Lines.Clear;

  LListDouble := TList<Double>.Create;
  try
    // テスト用のデータを格納
    for LIndex := 0 to 4 do begin
      LValue := (LIndex + 1) * 100.0 + 0.5;
      LListDouble.Add(LValue);
    end;

    LStream := TMemoryStream.Create;
    try
      for LIndex := 0 to LListDouble.Count - 1 do begin
        // TList に格納したデータをメモリストリームに書き込む
        LValue := LListDouble[LIndex];
        LStream.WriteData(LValue);
        // または
        // LStream.Write(@LValue, SizeOf(LValue));
      end;
      // メモリストリームをファイルに保存
      LStream.SaveToFile(LFilePath);
    finally
      FreeAndNil(LStream);
    end;
  finally
    FreeAndNil(LListDouble);
  end;
end;

//=====================================================================================
//  ファイルからデータを読み込んでジェネリックスの TList に格納
//  uses に Generics.Collections が必要
//=====================================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LListDouble : TList<Double>;
  LValue      : Double;
  LStream     : TMemoryStream;
  LFilePath   : string;
  LIndex      : Integer;
begin
  LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin';
  if not FileExists(LFilePath) then Exit;

  Memo1.Lines.Clear;

  LListDouble := TList<Double>.Create;
  try
    LStream := TMemoryStream.Create;
    try
      // メモリストリームにファイルの内容をロードする
      LStream.LoadFromFile(LFilePath);

      while True do begin
        // メモリストリームからデータを読み込む
        LStream.ReadData(LValue);
        // 読み込んだデータを TList に格納
        LListDouble.Add(LValue);

        if LStream.Position >= LStream.Size then Break;
      end;
    finally
      FreeAndNil(LStream);
    end;

    // 格納したデータを表示
    for LIndex := 0 to LListDouble.Count - 1 do begin
      LValue := LListDouble[LIndex];
      Memo1.Lines.Add(FloatToStr(LValue));
    end;
  finally
    FreeAndNil(LListDouble);
  end;
end;




21_TList<T> のデータをファイルに保存 - ファイル型を使用


ファイル型を使用して TList<T> のデータをファイルに保存し,読み込む時もファイル型を使用して,ファイルからデータを読み出して TList<T> の要素に代入する例です.
ファイル型を Byte 型にしているので,前項のサンプルで保存したデータを読み込むことができます.また,本サンプルで保存したデータを前項のサンプルで読み出すこともてぎす.


リスト18
TList<T> のデータのファイルへの保存とファイルからの読み込み.ファイル型を使用する例
//=====================================================================================
//  ジェネリックスの TList に実数値を格納してファイルに保存
//  ファイル型を使用する例
//  uses に Generics.Collections が必要
//=====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LFilePath   : string;
  LListDouble : TList<Double>;
  LValue      : Double;
  LFile       : File of Byte;
  LIndex      : Integer;
begin
  LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin';

  Memo1.Lines.Clear;

  LListDouble := TList<Double>.Create;
  try
    // テスト用のデータを格納
    for LIndex := 0 to 4 do begin
      LValue := (LIndex + 1) * 100.0 + 0.5;
      LListDouble.Add(LValue);
    end;

    // 保存ファイル名をファイル型の変数に割り当てる
    AssignFile(LFile, LFilePath);
    try
      // 新規にファイルを作成して開く(既存ファイルは上書き)
      Rewrite(LFile);

      // データを順番にファイルに書き込む
      for LIndex := 0 to LListDouble.Count - 1 do begin
        LValue := LListDouble[LIndex];
        BlockWrite(LFile, LValue, SizeOf(LValue));
      end;
    finally
      // ファイルを閉じる
      CloseFile(LFile);
    end;
  finally
    FreeAndNil(LListDouble);
  end;
end;

//=====================================================================================
//  ファイルからデータを読み込んでジェネリックスの TList に格納
//  ファイル型を使用する例
//  uses に Generics.Collections が必要
//=====================================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LFilePath   : string;
  LListDouble : TList<Double>;
  LValue      : Double;
  LFile       : File of Byte;
  LIndex      : Integer;
begin
  LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin';
  if not FileExists(LFilePath) then Exit;

  Memo1.Lines.Clear;

  LListDouble := TList<Double>.Create;
  try
    // 保存ファイル名をファイル型の変数に割り当てる
    AssignFile(LFile, LFilePath);
    try
      // ファイルを開く
      Reset(LFile);
      // ファイルからデータを読み込んでリストに追加
      while True do begin
        if Eof(LFile) then break;

        BlockRead(LFile, LValue, SizeOf(LValue));
        LListDouble.Add(LValue);
      end;
    finally
      // ファイルを閉じる
      CloseFile(LFile);
    end;

    // 格納したデータを表示
    for LIndex := 0 to LListDouble.Count - 1 do begin
      LValue := LListDouble[LIndex];
      Memo1.Lines.Add(FloatToStr(LValue));
    end;
  finally
    FreeAndNil(LListDouble);
  end;
end;




22_TList<T> のデータをファイルに保存 - レコード型


TList<T> に格納しているレコード型のデータのファイルへの保存と読み出しのサンプルです.

TList<T> に格納されているデータをファイルに保存して読み出す場合,TList<T> に格納されているデータが固定長の場合に限ります.String 型は可変長なので使用できません.このサンプルでは,レコード型の文字列のメンバとして Char 型の静的な文字配列を使用しています.
この Char 型の静的な文字配列は,Windows API の構造体でもよく使用されています.



図19
ァイルに保存したデータを読み込んで表示

リスト19
TList<T> のレコード型データをファイルに保存
implementation

uses
  System.StrUtils;

{$R *.dfm}

type
  TDataRec = record
    Text  : array [0..29] of Char;
    Value : Integer;
  end;
  PDataRec = ^TDataRec;

//=====================================================================================
//  ジェネリックスの TList にレコード型のデータを格納してファイルに保存
//  uses に Generics.Collections, System.StrUtils が必要
//=====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LListRec  : TList<TDataRec>;
  LDataRec  : TDataRec;
  LStream   : TMemoryStream;
  LFilePath : string;
  LIndex    : Integer;
begin
  LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin';

  Memo1.Lines.Clear;

  LListRec := TList<TDataRec>.Create;
  try
    // テスト用のデータを格納
    for LIndex := 0 to 4 do begin
      // レコード型の初期化
      // 初期化を忘れると正しいデータとして格納できないことがある
      LDataRec := System.Default(TDataRec);
      // string 型の文字列を Char 型の文字配列にコピー
      StrPCopy(LDataRec.Text, DupeString('文字列', LIndex + 1));

      LDataRec.Value := (LIndex + 1) * 100;
      LListRec.Add(LDataRec);
    end;

    LStream := TMemoryStream.Create;
    try
      for LIndex := 0 to LListRec.Count - 1 do begin
        // TList に格納したデータをメモリストリームに書き込む
        LDataRec := LListRec[LIndex];
        LStream.Write(@LDataRec, SizeOf(LDataRec));
      end;
      // メモリストリームをファイルに保存
      LStream.SaveToFile(LFilePath);
    finally
      FreeAndNil(LStream);
    end;
  finally
    FreeAndNil(LListRec);
  end;
end;

//=====================================================================================
//  ファイルからデータを読み込んでジェネリックスの TList に格納
//  uses に Generics.Collections が必要
//=====================================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LListRec  : TList<TDataRec>;
  LDataRec  : TDataRec;
  LStream   : TMemoryStream;
  LFilePath : string;
  LText     : string;
  LIndex    : Integer;
begin
  LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin';
  if not FileExists(LFilePath) then Exit;

  Memo1.Lines.Clear;

  LListRec := TList<TDataRec>.Create;
  try
    LStream := TMemoryStream.Create;
    try
      // メモリストリームにファイルの内容をロードする
      LStream.LoadFromFile(LFilePath);

      while True do begin
        // メモリストリームからデータを読み込む
        LStream.Read(LDataRec, SizeOf(LDataRec));
        // 読み込んだデータを TList に格納
        LListRec.Add(LDataRec);

        if LStream.Position >= LStream.Size then Break;
      end;
    finally
      FreeAndNil(LStream);
    end;

    // 格納したデータを表示
    for LIndex := 0 to LListRec.Count - 1 do begin
      LDataRec := LListRec[LIndex];
      LText := IntToStr(LDataRec.Value) + '  ' + string(LDataRec.Text);
      Memo1.Lines.Add(LText);
    end;
  finally
    FreeAndNil(LListRec);
  end;
end;




23_レコード型の保存と String 型や動的配列の変数


レコード型のメンバに String 型の変数があった場合,レコードごとにそれらの文字列の長さが違っていても,SizeOf 関数で取得するレコード型の変数のサイズは同じです.

String 型の変数はポインタです.レコード型のメンバとしての String 型の変数のサイズは,このポインタのサイズとなります.ポインタのサイズは 32 ビットの EXE では 4 バイトです.ストリームに書き込むと,String 型の変数の位置にはポインタとしてのメモリアドレスの値だけが書き込まれます.レコード型の String 型の文字列はストリームには書き込まれません.したがって,保存して読みも出しても文字列としてのデータは取得できません.通常は例外が発生します.

PChar 型,動的配列,クラス型等の変数も同じです.数値型の静的配列であれば,ストリームに全ての要素のデータを書き込むことができます.静的配列はポインタではありません.

このことから,レコード型の文字列のメンバとして,一般的に,要素数が固定の Char 型の静的な文字配列が使用されています.
String 型や動的配列等のメンバも含めて保存しいた場合は,TComponent の派生クラスを生成して,それをリストに格納する方法があります.