Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2018年08月22日] [更新 2022年08月15日] Delphi 一般・その他
   
数値型の配列データのファイルへの保存と読み出し
動作確認等 Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32
後半は,Windows 10 ビルド 19043 + Delphi XE5(UP2) Pro VCL-64
[整理番号 - 0710]







数値型の配列データのファイルへの保存と読み出し


整数型の配列データや実数型の配列データも,単一の数値と同じようにファイルに保存して,保存したファイルから読み出すことができます.
配列そのものをファイルとしてディスクに保存することはできません.配列の要素の値をメモリストリームに格納すれば,そのメモリストリームをファイルに保存できます.ファイルから読み込む時は,メモリストリームに読み出して,そのメモリストリームから配列要素の値としてのデータを取り出すことができます.

当然ですが,保存したファイル,あるいはメモリストリームに格納したデータは,単なるバイトデータです.そのデータが配列のデータであるという情報はありません.






01_1 次元の静的配列データのファイルへの保存と読み出し


1 次元の静的配列データのファイルへの保存と読み出しの例です.
配列要素のデータは,メモリうえで連続して書き込まれています.配列の先頭アドレスとメモリストリームに格納するバイト数を指定して TMemoryStream.Write を実行します.



図1
ファイルから 1 次元の静的配列データを読み出して表示

リスト1
1 次元の静的配列データのファイルへの保存と読み出し
implementation

{$R *.dfm}

const
  FColCount = 5;

type
  TDoubleArray = array [0..FColCount - 1] of Double;


//=============================================================================
//  1 次元の静的配列データをファイルへ保存
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LSrcDblArray : TDoubleArray;
  LCol         : Integer;
  LMemStream   : TMemoryStream;
begin
  // テスト用のデータを配列要素に代入
  for LCol := 0 to Length(LSrcDblArray) - 1 do begin
    LSrcDblArray[LCol] := 100 + (LCol + 1) / 10.0;
  end;

   LMemStream := TMemoryStream.Create;
  try
    // メモリストリームに配列データを格納
    LMemStream.Size := SizeOf(LSrcDblArray);
    LMemStream.Write(@LSrcDblArray, LMemStream.Size);

   // そのメモリストリームをファイルに保存
    LMemStream.SaveToFile('1dim-StaticArray.bin');
  finally
    FreeAndNil(LMemStream);
  end;
end;

//=============================================================================
//  1 次元の静的配列データをファイルから読み出す
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LMemStream   : TMemoryStream;
  LDstDblArray : TDoubleArray;
  LCol         : Integer;
  LResText     : string;
begin
  Memo1.Lines.Clear;

  LMemStream := TMemoryStream.Create;
  try
    // ファイルのデータをメモリストリームに読み込む
    LMemStream.LoadFromFile('1dim-StaticArray.bin');

    // メモリストリームのデータを配列要素に読み込む
    LMemStream.Position := 0;
    LMemStream.Read(LDstDblArray, LMemStream.Size);

    // 取得したデータを表示
    LResText := '';
    for LCol := 0 to FColCount - 1 do begin
      LResText := LResText + FormatFloat('   000.0', LDstDblArray[LCol]);
    end;
    Memo1.Lines.Add(LResText);
  finally
    FreeAndNil(LMemStream);
  end;
end;




02_2 次元の静的配列データのファイルへの保存と読み出し


2 次元の静的配列データのファイルへの保存と読み出しの例です.
2 次元の静的配列の各要素の値は,1 行目の各要素の値の後に 2 行目以降の各要素の値が連続してメモリに格納されています.それを TMemoryStream に格納して,その TMemoryStream をファイルとしてディスクに保存します.



図2
ファイルから 2 次の静的配列データを読み出して表示

リスト2
2 次元の静的配列データのファイルへの保存と読み出し
implementation

{$R *.dfm}

const
  FRowCount = 2;
  FColCount = 5;

type
  TDoubleArray = array [0..FRowCount - 1, 0..FColCount - 1] of Double;


//=============================================================================
//  2 次元の静的配列データをファイルへ保存
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LSrcDblArray : TDoubleArray;
  LRow         : Integer;
  LCol         : Integer;
  LMemStream   : TMemoryStream;
begin
  // テスト用のデータを配列要素に代入
  for LRow := 0 to FRowCount - 1 do begin
    for LCol := 0 to FColCount - 1 do begin
      LSrcDblArray[LRow][LCol] := (LRow + 1) * 100 + (LCol + 1) / 10.0;
    end;
  end;

   LMemStream := TMemoryStream.Create;
  try
    // メモリストリームに配列データを格納
    LMemStream.Size := SizeOf(LSrcDblArray);
    LMemStream.Write(@LSrcDblArray, LMemStream.Size);

   // そのメモリストリームをファイルに保存
    LMemStream.SaveToFile('2dim-StaticArray.bin');
  finally
    FreeAndNil(LMemStream);
  end;
end;

//=============================================================================
//  2 次元の静的配列データをファイルから読み出す
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LMemStream   : TMemoryStream;
  LDstDblArray : TDoubleArray;
  LRow         : Integer;
  LCol         : Integer;
  LResText     : string;
begin
  Memo1.Lines.Clear;

  LMemStream := TMemoryStream.Create;
  try
    // ファイルのデータをメモリストリームに読み込む
    LMemStream.LoadFromFile('2dim-StaticArray.bin');

    // メモリストリームのデータを配列要素に読み込む
    LMemStream.Position := 0;
    LMemStream.Read(LDstDblArray, LMemStream.Size);

    // 取得したデータを表示
    for LRow := 0 to FRowCount - 1 do begin
      LResText := '';
      for LCol := 0 to FColCount - 1 do begin
        LResText := LResText + FormatFloat('   000.0', LDstDblArray[LRow][LCol]);
      end;
      Memo1.Lines.Add(LResText);
    end;
  finally
    FreeAndNil(LMemStream);
  end;
end;




03_1 次元の動的配列データのファイルへの保存と読み出し


1 次元の動的配列データのファイルへの保存と読み出しの例です.
動的配列はポインタです.最初の要素のデータのアドレスを指定して,TMemoryStream に要素数分のデータを格納します.その TMemoryStream をファイルとしてディスクに保存します.

ディスク上のファイルから TMemoryStream にデータを読み込むと,それは配列要素全体のバイト数です.それを要素の変数の型のサイズで割ると配列の要素数になります.この値を使用して動的配列の要素数を設定します.
TMemoryStream のデータを配列に読み込む時は,読み込み開始位置として配列の先頭の要素を指定します.動的配列はポインタです.ポインタに要素の値をセットしても意味がありません.例外が発生するだけです.



図3
ファイルから 1 次元の動的配列データを読み出して表示

リスト3
1 次元の動的配列データのファイルへの保存と読み出し
implementation

uses
  System.Types;

{$R *.dfm}

//=============================================================================
//  1 次元の動的配列データをファイルへ保存
//  TDoubleDynArray の使用には uses に System.Types が必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
const
  LColCount : Integer = 5;
var
  LSrcDblArray : TDoubleDynArray;
  LCol         : Integer;
  LMemStream   : TMemoryStream;
begin
  SetLength(LSrcDblArray, LColCount);

  // テスト用のデータを配列要素に代入
  for LCol := 0 to LColCount - 1 do begin
    LSrcDblArray[LCol] := 100 + (LCol + 1) / 10.0;
  end;

   LMemStream := TMemoryStream.Create;
  try
    // メモリストリームに配列データを格納
    // 動的配列はポインタなので最初の要素のアドレスを指定する
    LMemStream.Size := LColCount * SizeOf(Double);
    LMemStream.Write(@LSrcDblArray[0], LMemStream.Size);

   // そのメモリストリームをファイルに保存
    LMemStream.SaveToFile('1dim-DynArray.bin');
  finally
    FreeAndNil(LMemStream);
  end;
end;

//=============================================================================
//  1 次元の動的配列データをファイルから読み出す
//  TDoubleDynArray の使用には uses に System.Types が必要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LMemStream   : TMemoryStream;
  LDstDblArray : TDoubleDynArray;
  LColCount    : Integer;
  LCol         : Integer;
  LResText     : string;
begin
  Memo1.Lines.Clear;

  LMemStream := TMemoryStream.Create;
  try
    // ファイルのデータをメモリストリームに読み込む
    LMemStream.LoadFromFile('1dim-DynArray.bin');

    // 配列の要素数を計算
    LColCount := LMemStream.Size div SizeOf(Double);
    // 配列の要素数を設定 (配列用のメモリの確保)
    SetLength(LDstDblArray, LColCount);

    // メモリストリームのデータを配列要素に読み込む
    // 動的配列はポインタなので最初の要素の値を指定する
    LMemStream.Position := 0;
    LMemStream.Read(LDstDblArray[0], LMemStream.Size);

    // 取得したデータを表示
    LResText := '';
    for LCol := 0 to LColCount - 1 do begin
      LResText := LResText + FormatFloat('   000.0', LDstDblArray[LCol]);
    end;
    Memo1.Lines.Add(LResText);
  finally
    FreeAndNil(LMemStream);
  end;
end;




04_2 次元の動的配列データのファイルへの保存と読み出し


2 次元の動的配列データのファイルへの保存と読み出しの例です.
ファイルから配列データを読み込んだ時,動的配列のサイズを設定するのに配列の行数と列数の値が必要です.そこでこのサンプルでは,行数と列数の値を TMemoryStream に格納してから実際の配列要素の値をその TMemoryStream に格納するようにしています.その TMemoryStream をファイルとしてディスクに保存します..

2 次元の動的配列は,その各行が 1 次元配列のポインタとなっています.そのため,TMemoryStream に配列要素の値を格納する時は行単位で行います.各行の最初の要素のデータのアドレスを指定して,1 行分の配列テータを TMemoryStream に格納していきます.



図4
ファイルから 2 次元の動的配列データを読み出して表示

リスト4
2 次元の動的配列データのファイルへの保存と読み出し
implementation

{$R *.dfm}

type
  TDouble2DDynArray = array of array of Double;


//=============================================================================
//  2 次元の動的配列データをファイルへ保存
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
const
  LRowCount : Integer = 3;
  LColCount : Integer = 5;
var
  LSrcDblArray : TDouble2DDynArray;
  LRow         : Integer;
  LCol         : Integer;
  LMemStream   : TMemoryStream;
begin
  // 配列の要素数を設定して,テスト用のデータを配列要素に代入
  SetLength(LSrcDblArray, LRowCount, LColCount);
  for LRow := 0 to LRowCount - 1 do begin
    for LCol := 0 to LColCount - 1 do begin
      LSrcDblArray[LRow][LCol] := (LRow + 1) * 100 + (LCol + 1) / 10.0;
    end;
  end;

   LMemStream := TMemoryStream.Create;
  try
   // メモリストリームに行数と列数の値を格納
    LMemStream.WriteData(LRowCount);
    LMemStream.WriteData(LColCount);

    // メモリストリームに配列データを格納
    // 動的配列の各行はポインタなので各行の最初の要素のアドレスを指定する
    for LRow := 0 to LRowCount - 1 do begin
      LMemStream.Write(@LSrcDblArray[LRow][0], LColCount * SizeOf(Double));
    end;

    // そのメモリストリームをファイルに保存
    LMemStream.SaveToFile('2dim-DynArray.bin');
  finally
    FreeAndNil(LMemStream);
  end;
end;

//=============================================================================
//  2 次元の動的配列データをファイルから読み出す
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LMemStream   : TMemoryStream;
  LDstDblArray : TDouble2DDynArray;
  LRowCount    : Integer;
  LColCount    : Integer;
  LRow         : Integer;
  LCol         : Integer;
  LResText     : string;
begin
  Memo1.Lines.Clear;

  LMemStream := TMemoryStream.Create;
  try
    // ファイルのデータをメモリストリームに読み込む
    LMemStream.LoadFromFile('2dim-DynArray.bin');

    // 配列の行数と列数の値をメモリストリームから読み出す
    LMemStream.Position := 0;
    LMemStream.ReadData(LRowCount);
    LMemStream.ReadData(LColCount);
    // 配列の行数と列数を設定 (配列用のメモリの確保)
    SetLength(LDstDblArray, LRowCount, LColCount);

    // メモリストリームのデータを配列要素に読み込む
    // 動的配列の各行はポインタなので,各行の先頭要素を指定して行単位で読み込む
    for LRow := 0 to LRowCount - 1 do begin
      LMemStream.Read(LDstDblArray[LRow][0], LColCount * SizeOf(Double));
    end;

    // 取得したデータを表示
    for LRow := 0 to LRowCount - 1 do begin
      LResText := '';
      for LCol := 0 to LColCount - 1 do begin
        LResText := LResText + FormatFloat('   000.0', LDstDblArray[LRow][LCol]);
      end;
      Memo1.Lines.Add(LResText);
    end;
  finally
    FreeAndNil(LMemStream);
  end;
end;




05_2 GB を超える配列データのファイルへの保存と読み出し


ここまでのサンプルでは,保存と読み出しに TMemorySteam を使用しています.TMemoryStream は,32 ビット,64 ビットに関係なく,2 GB を超えるデータは扱えません.2 GB 以上のデータを扱うには,例えば,TFileStream を使用することができます.その例です.
このサンプルは,32 ビットの EXE では動作しません.



図5
実行結果
  • EXE は 64 ビット
  • 配列の要素は 24575 x 24575
  • 配列データのサイズ約 4.8 GB
  • [メモリ確保] [保存] [読み込み] の順に実行
  • 保存に要した時間は 10.3 秒

リスト5
2 GB を超える動的配列データのファイルへの保存と読み出し
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Button2: TButton;
    Button3: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;
var
  Form1: TForm1;

implementation

uses
  Winapi.MMSystem;

{$R *.dfm}

type
  T2DDynArray = array of array of Double;
const
  FSize : NativeInt = $5FFF;
var
  FArray1   : T2DDynArray;
  FFilePath : string;

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  FFilePath := ExtractFilePath(Application.ExeName) + '000.dat';
end;

//=============================================================================
//  動的配列のメモリ確保
//  2 GB を超える 2 次元の動的配列の例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LTime  : Cardinal;
  LBytes : UInt64;
  I      : NativeInt;
  J      : NativeInt;
begin
  Screen.Cursor := crHourGlass;
  Memo1.Lines.Clear;
  Memo1.Update;

  LTime := timeGetTime;

  LBytes := FSize * FSize * SizeOf(Double);
  Memo1.Lines.Add(FormatFloat(',0', LBytes) + ' Byte');

  SetLength(FArray1, FSize, FSize);

  //  動的配列の要素に数値を代入
  for I := 0 to Length(FArray1) - 1 do begin
    for J := 0 to Length(FArray1[I]) - 1 do begin
      FArray1[I][J] := 1.0;
    end;
  end;
  FArray1[0][0] := 123.456;
  FArray1[FSize - 1][FSize - 1] := 654.123;

  Memo1.Lines.Add(FormatFloat('#0.000 sec', (timeGetTime - LTime)  / 1000));
  Memo1.Lines.Add('End');
  Screen.Cursor := crDefault;
end;

//=============================================================================
//  2 次元の動的配列のデータをファイルに保存
//  TFileStream を使用する例
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LTime       : Cardinal;
  I           : NativeInt;
  LFileStream : TFileStream;
begin
  if Length(FArray1) = 0 then begin
    Exit;
  end;

  Memo1.Lines.Add('');
  Screen.Cursor := crHourGlass;
  LTime := timeGetTime;

 //  ファイルに保存
  LFileStream := TFileStream.Create(FFilePath, fmCreate);
  try
    for I := 0 to Length(FArray1) - 1 do begin
      LFileStream.Write(FArray1[I][0], Length(FArray1[I]) * SizeOf(Double));
    end;
  finally
    FreeAndNil(LFileStream);
  end;

  Memo1.Lines.Add(FormatFloat('#0.000 sec', (timeGetTime - LTime)  / 1000));
  Memo1.Lines.Add('End -- SaveData');
  Screen.Cursor := crDefault;
end;

//=============================================================================
//  2 次元の動的配列のデータをファイルから読み込む
//  TFileStream を使用する例
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LTime       : Cardinal;
  I           : NativeInt;
  LFileStream : TFileStream;
  Lvalue  : Double;
begin
  if not FileExists(FFilePath) then begin
    Exit;
  end;
  if Length(FArray1) = 0 then begin
    Exit;
  end;
  FArray1[0][0] := 0;
  FArray1[FSize - 1][FSize - 1] := 0;


  Memo1.Lines.Add('');
  Screen.Cursor := crHourGlass;
  LTime := timeGetTime;

  //  ファイルから読み込む
  LFileStream := TFileStream.Create(FFilepath, fmOpenRead);
  try
    for I := 0 to Length(FArray1) - 1 do begin
      LFileStream.Read(FArray1[I][0], Length(FArray1[I]) * SizeOf(Double));
    end;
  finally
    FreeAndNil(LFileStream);
  end;

  // 確認のために特定要素の値を表示
  Lvalue := FArray1[0][0];
  Memo1.Lines.Add(FormatFloat(',0.000', Lvalue));
  Lvalue := FArray1[FSize - 1][FSize - 1];
  Memo1.Lines.Add(FormatFloat(',0.000', Lvalue));

  Memo1.Lines.Add(FormatFloat('#0.000 sec', (timeGetTime - LTime)  / 1000));
  Memo1.Lines.Add('End -- ReadData');
  Screen.Cursor := crDefault;
end;

end.


上のサンプルは,行単位で処理しています.以下は,配列の要素単位で処理した場合です.
上のサンプルでは,保存に要した士時間は 10 秒でしたが,こちらは 805 秒かかっています.



図6
要素単位で処理した結果
  • EXE は 64 ビット
  • 配列の要数素は 24575 x 24575
  • 配列データのサイズ約 4.8 GB
  • [メモリ確保] [保存] [読み込み] の順に実行
  • 保存に要した時間は 805.9 秒 (13 分 25.9 秒)

リスト6
2 GB を超える動的配列データのファイルへの保存と読み出し.配列の要素単位で処理
//=============================================================================
//  2 次元の動的配列のデータをファイルに保存
//  要素単位で保存する場合
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LTime       : Cardinal;
  I           : NativeInt;
  J           : NativeInt;
  LFileStream : TFileStream;
begin
  if Length(FArray1) = 0 then begin
    Exit;
  end;


  Memo1.Lines.Add('');
  Screen.Cursor := crHourGlass;
  LTime := timeGetTime;

 //  ファイルに保存
  LFileStream := TFileStream.Create(FFilePath, fmCreate);
  try
    for I := 0 to Length(FArray1) - 1 do begin
      for J := 0 to Length(FArray1[I]) - 1 do begin
        LFileStream.Write(FArray1[I][J], SizeOf(Double));
      end;
    end;
  finally
    FreeAndNil(LFileStream);
  end;

  Memo1.Lines.Add(FormatFloat('#0.000 sec', (timeGetTime - LTime)  / 1000));
  Memo1.Lines.Add('End -- SaveData');
  Screen.Cursor := crDefault;
end;

//=============================================================================
//  2 次元の動的配列のデータをファイルから読み込む
//  要素単位で読み込む場合
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LTime       : Cardinal;
  I           : NativeInt;
  J           : NativeInt;
  LFileStream : TFileStream;
  Lvalue  : Double;
begin
  if not FileExists(FFilePath) then begin
    Exit;
  end;
  if Length(FArray1) = 0 then begin
    Exit;
  end;
  FArray1[0][0] := 0;
  FArray1[FSize - 1][FSize - 1] := 0;


  Memo1.Lines.Add('');
  Screen.Cursor := crHourGlass;
  LTime := timeGetTime;

  //  ファイルから読み込む
  LFileStream := TFileStream.Create(FFilepath, fmOpenRead);
  try
    for I := 0 to Length(FArray1) - 1 do begin
      for J := 0 to Length(FArray1[I]) - 1 do begin
        LFileStream.Read(FArray1[I][J], SizeOf(Double));
      end;
    end;
  finally
    FreeAndNil(LFileStream);
  end;

  // 確認のために特定要素の値を表示
  Lvalue := FArray1[0][0];
  Memo1.Lines.Add(FormatFloat(',0.000', Lvalue));
  Lvalue := FArray1[FSize - 1][FSize - 1];
  Memo1.Lines.Add(FormatFloat(',0.000', Lvalue));

  Memo1.Lines.Add(FormatFloat('#0.000 sec', (timeGetTime - LTime)  / 1000));
  Memo1.Lines.Add('End -- ReadData');
  Screen.Cursor := crDefault;
end;


下図は,配列の要素数を 4095 x 4095 ($FFF x $FFF) にして,テータのサイズを約 32 分の 1 にした結果です.それでも保存に 22.4 秒かかっています.



図7
要素単位で処理した結果
  • 配列の要素数は 4095 x 4095 ($FFF x $FFF)
  • 配列データのサイズ約 134 MB
  • [メモリ確保] [保存] [読み込み] の順に実行
  • 保存に要した時間は 22.4 秒

本項の最初のサンプルと同じように,行単位で処理すると下図の結果になります.保存に要した時間が 0.094 秒と,大幅に短くなりました.

仮想メモリを多く使用するようなサイズの配列の場合は,行単位で処理しても処理時間の短縮は期待できません.仮想メモリのドライブが SSD であれば,処理時間の短縮が期待できるかも知れません.



図8
行単位で処理した結果
  • 配列の要素数は 4095 x 4095 ($FFF x $FFF)
  • 配列データのサイズ約 134 MB
  • 行単位で TMemoryStream を使用した場合
  • 保存に要した時間は 0.094 秒




06_ファイル型 File of Double による配列データの保存と読み出し


ファイル型を使用してファイルにアクセスすれば,2 GB を超える動的配列のデータが扱えます.
以下は,前項のサンプルをファイル型で処理するようにしたサンプルです.型を File of Double としています.ファイル型は,TFileStream とは違い,ヘッダ部等はありません.TMemoryStream と同じように,純粋にデータだけを保存します.
このサンプルは,32 ビットの EXE では動作しません.
当然ですが,配列の要素単位で処理すれば,処理には相当の時間がかかります.



図9
ファイル型を使用して配列データを保存
  • EXE は 64 ビット
  • 配列の要素は 24575 x 24575
  • 配列データのサイズ約 4.8 GB
  • [メモリ確保] [保存] [読み込み] の順に実行

リスト7
ファイル型 File of Double による配列データの保存と読み出し.2 GB 以上の配列データも処理可能
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Button2: TButton;
    Button3: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;
var
  Form1: TForm1;

implementation

uses
  Winapi.MMSystem;

{$R *.dfm}

type
  T2DDynArray = array of array of Double;
const
  FSize : NativeInt = $5FFF;
var
  FArray1   : T2DDynArray;
  FFilePath : string;

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  FFilePath := ExtractFilePath(Application.ExeName) + '000.dat';
end;

//=============================================================================
//  動的配列のメモリ確保
//  2 GB を超える 2 次元の動的配列の例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LTime  : Cardinal;
  LBytes : UInt64;
  I      : NativeInt;
  J      : NativeInt;
begin
  Screen.Cursor := crHourGlass;
  Memo1.Lines.Clear;
  Memo1.Update;

  LTime := timeGetTime;

  LBytes := FSize * FSize * SizeOf(Double);
  Memo1.Lines.Add(FormatFloat(',0', LBytes) + ' Byte');

  SetLength(FArray1, FSize, FSize);

  //  動的配列の要素に数値を代入
  for I := 0 to Length(FArray1) - 1 do begin
    for J := 0 to Length(FArray1[I]) - 1 do begin
      FArray1[I][J] := 1.0;
    end;
  end;
  FArray1[0][0] := 123.456;
  FArray1[FSize - 1][FSize - 1] := 654.123;

  Memo1.Lines.Add(FormatFloat('#0.000 sec', (timeGetTime - LTime)  / 1000));
  Memo1.Lines.Add('End');
  Screen.Cursor := crDefault;
end;

//=============================================================================
//  2 次元の動的配列のデータをファイルに保存
//  ファイル型 File of Double を使用する例
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LTime : Cardinal;
  I     : NativeInt;
  LFile : File of Double;
begin
  if Length(FArray1) = 0 then begin
    Exit;
  end;

  Memo1.Lines.Add('');
  Screen.Cursor := crHourGlass;
  LTime := timeGetTime;


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

    for I := 0 to Length(FArray1) - 1 do begin
      // データをファイルに書き込む
      BlockWrite(LFile, FArray1[I][0], Length(FArray1[I]));
    end;
  finally
    // ファイルを閉じる
    CloseFile(LFile);
  end;

  Memo1.Lines.Add(FormatFloat('#0.000 sec', (timeGetTime - LTime)  / 1000));
  Memo1.Lines.Add('End -- SaveData');
  Screen.Cursor := crDefault;
end;

//=============================================================================
//  2 次元の動的配列のデータをファイルから読み込む
//  ファイル型 File of Double を使用する例
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LTime  : Cardinal;
  I      : NativeInt;
  LFile  : File of Double;
  Lvalue : Double;
begin
  if not FileExists(FFilePath) then begin
    Exit;
  end;
  if Length(FArray1) = 0 then begin
    Exit;
  end;
  FArray1[0][0] := 0;
  FArray1[FSize - 1][FSize - 1] := 0;


  Memo1.Lines.Add('');
  Screen.Cursor := crHourGlass;
  LTime := timeGetTime;

  // 保存ファイル名をファイル型の変数に割り当てる
  AssignFile(LFile, FFilePath);
  try
    // ファイルを開く
    Reset(LFile);

    // ファイルからデータを読み込む
    for I := 0 to Length(FArray1) - 1 do begin
      BlockRead(LFile, FArray1[I][0], Length(FArray1[I]));
    end;
  finally
    // ファイルを閉じる
    CloseFile(LFile);
  end;

  // 確認のために特定要素の値を表示
  Lvalue := FArray1[0][0];
  Memo1.Lines.Add(FormatFloat(',0.000', Lvalue));
  Lvalue := FArray1[FSize - 1][FSize - 1];
  Memo1.Lines.Add(FormatFloat(',0.000', Lvalue));

  Memo1.Lines.Add(FormatFloat('#0.000 sec', (timeGetTime - LTime)  / 1000));
  Memo1.Lines.Add('End -- ReadData');
  Screen.Cursor := crDefault;
end;

end.




07_FileWrite / FileRead 関数で配列データの保存と読み出し


FileWrite 関数や FileRead 関数を使用してファイルにアクセスすれば,2 GB 以上の動的配列データを扱えます.そのサンプルです.これらの関数類は,内部で Windows API の関数を使用して処理しています.
このサンプルは,32 ビットの EXE では動作しません.



図10
FileWrite / FileRead 関数を使用した結果
  • EXE は 64 ビット
  • 配列の要素は 24575 x 24575
  • 配列データのサイズ約 4.8 GB
  • [メモリ確保] [保存] [読み込み] の順に実行

リスト8
FileWrite 関数や FileRead 関数を使用して,配列データの保存と読み出しを行う
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Button2: TButton;
    Button3: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;
var
  Form1: TForm1;

implementation

uses
  Winapi.MMSystem;

{$R *.dfm}

type
  T2DDynArray = array of array of Double;
const
  FSize : NativeInt = $5FFF;
var
  FArray1   : T2DDynArray;
  FFilePath : string;

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  FFilePath := ExtractFilePath(Application.ExeName) + '000.dat';
end;

//=============================================================================
//  動的配列のメモリ確保
//  2 GB を超える 2 次元の動的配列の例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LTime  : Cardinal;
  LBytes : UInt64;
  I      : NativeInt;
  J      : NativeInt;
begin
  Screen.Cursor := crHourGlass;
  Memo1.Lines.Clear;
  Memo1.Update;

  LTime := timeGetTime;

  LBytes := FSize * FSize * SizeOf(Double);
  Memo1.Lines.Add(FormatFloat(',0', LBytes) + ' Byte');

  SetLength(FArray1, FSize, FSize);

  // 動的配列の要素に数値を代入
  for I := 0 to Length(FArray1) - 1 do begin
    for J := 0 to Length(FArray1[I]) - 1 do begin
      FArray1[I][J] := 1.0;
    end;
  end;
  FArray1[0][0] := 123.456;
  FArray1[FSize - 1][FSize - 1] := 654.123;

  Memo1.Lines.Add(FormatFloat('#0.000 sec', (timeGetTime - LTime)  / 1000));
  Memo1.Lines.Add('End');
  Screen.Cursor := crDefault;
end;

//=============================================================================
//  2 次元の動的配列のデータをファイルに保存
//  FileCreate. FileWrite を使用する例
//
//  FileCreate は Windows API の CreateFile 関数でファイルを作成する
//  FileWrite は Windows API の WriteFile 関数を使用している
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LTime    : Cardinal;
  I        : NativeInt;
  LFHandle : THandle;
begin
  if Length(FArray1) = 0 then begin
    Exit;
  end;

  Memo1.Lines.Add('');
  Screen.Cursor := crHourGlass;
  LTime := timeGetTime;


  // 保存ファイル名のファイルを作成
  LFHandle := FileCreate(FFilePath);
  try
    for I := 0 to Length(FArray1) - 1 do begin
      // データをファイルに書き込む
      FileWrite(LFHandle, FArray1[I][0], Length(FArray1[I]) * SizeOf(Double));
    end;
  finally
    // ファイルを閉じる
    FileClose(LFHandle);
  end;

  Memo1.Lines.Add(FormatFloat('#0.000 sec', (timeGetTime - LTime)  / 1000));
  Memo1.Lines.Add('End -- SaveData');
  Screen.Cursor := crDefault;
end;

//=============================================================================
//  2 次元の動的配列のデータをファイルから読み込む
//  FileOpen, FileRead を使用する例
//
//  FileOpen は Windows API の OpenFile 関数を使用している
//  FileRead は Windows API の ReadFile 関数を使用している
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LTime   : Cardinal;
  I       : NativeInt;
  LFHandle : THandle;
begin
  if not FileExists(FFilePath) then begin
    Exit;
  end;
  if Length(FArray1) = 0 then begin
    Exit;
  end;
  FArray1[0][0] := 0;
  FArray1[FSize - 1][FSize - 1] := 0;


  Memo1.Lines.Add('');
  Screen.Cursor := crHourGlass;
  LTime := timeGetTime;

  // ファイルを開く
  LFHandle := FileOpen(FFilePath, fmOpenRead);
  try
    // ファイルからデータを読み込む
    for I := 0 to Length(FArray1) - 1 do begin
      FileRead(LFHandle, FArray1[I][0], Length(FArray1[I]) * SizeOf(Double));
    end;
  finally
    // ファイルを閉じる
    FileClose(LFHandle);
  end;

  Memo1.Lines.Add(FormatFloat('#0.000 sec', (timeGetTime - LTime)  / 1000));
  Memo1.Lines.Add('End -- ReadData');
  Screen.Cursor := crDefault;
end;

end.




08_3 次元の配列データのファイルへの保存と読み出し


3 次元の動的配列のデータは,2 次元の配列データのサンプルと同じ方法でファイルに保存したり,ファイルから読み込めます.
図の下のコードはその例です.下図は,上のサンプルと同じように,処理にかかった時間を表示するようにして実行した結果です.配列の要素数は,256 x 256 x 256 ( $100 x $100 X $100 ) にしています.データは Double 型としています.



図11
3 次元の動的配列のデータを保存
  • EXE は 64 ビット
  • 配列の要素数は 256 x 356 x 256
  • 配列データのサイズ約 134 MB
  • [メモリ確保] [保存] [読み込み] の順に実行
  • 保存に要した時間は 0.168 秒

要素単位で処理すると,どのぐらい時間がかかるかを調べてみました.保存に要した時間は,上の図では 0.168 秒になっていて,1 秒未満ですが,要素単位で処理すると 22.9 秒となりました.



図12
3 次元の動的的配列のデータを保存
  • 配列の要素単位で処理した場合
  • 保存に要した時間は 22.9 秒

リスト9
3 次元の動的配列のデータの保存と読み込みのコード
//=============================================================================
//  3 次元の動的配列のデータをファイルに保存
//
//  FArray1   : 3 次元の動的配列 (要素のデータは Double 型と仮定)
//  FFilePath : 保存先のファイル名
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  I        : NativeInt;
  J        : NativeInt;
  LFHandle : THandle;
begin
  // 保存ファイル名のファイルを作成
  LFHandle := FileCreate(FFilePath);
  try
    for I := 0 to Length(FArray1) - 1 do begin
      for J := 0 to Length(FArray1[I]) - 1 do begin
        // データをファイルに書き込む
        FileWrite(LFHandle, FArray1[I][J][0], Length(FArray1[I][J]) * SizeOf(Double));
      end;
    end;
  finally
    // ファイルを閉じる
    FileClose(LFHandle);
  end;
end;

//=============================================================================
//  3 次元の動的配列のデータをファイルから読み込む
//
//  FArray1   : 3 次元の動的配列 (要素のデータは Double 型と仮定)
//  FFilePath : 保存先のファイル名
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  I        : NativeInt;
  J        : NativeInt;
  LFHandle : THandle;
begin
  // ファイルを開く
  LFHandle := FileOpen(FFilePath, fmOpenRead);
  try
    // ファイルからデータを読み込む
    for I := 0 to Length(FArray1) - 1 do begin
      for J := 0 to Length(FArray1[I]) - 1 do begin
        FileRead(LFHandle, FArray1[I][J][0], Length(FArray1[I][J]) * SizeOf(Double));
      end;
    end;
  finally
    // ファイルを閉じる
    FileClose(LFHandle);
  end;
end;


静的配列のデータはメモリ上で連続しています.したがって,次のようなコードで処理できます.
下図は,前項のサンプルと同じように,処理にかかった時間を表示するようにして実行した結果です.配列の要素数は,256 x 256 x 256 ( $100 x $100 X $100 ) にしています.データは Double 型としています.

静的配列の場合,SizeOf(FArray1) の値は,配列 FArray1 のデータの全バイト数です.
動的配列の変数はポインタです.したがって,動的配列の場合,SizeOf(FArray1) の値は,ポインタのサイズ (バイト数) です.ポインタのサイズは,32 ビットの EXE では 4 バイトです.64 ビットの EXE では 8 バイトです.



図13
3 次元の静的配列のデータを保存
  • 配列の要数素は 256 x 256 x 256
  • 配列データのサイズ約 134 MB
  • [メモリ確保] [保存] [読み込み] の順に実行

リスト10
3 次元の静的配列のデータの保存と読み込みのコード
//=============================================================================
//  3 次元の静的配列のデータをファイルに保存
//
//  FArray1   : 3 次元の静的配列
//              要素のデータは Double 型と仮定
//              全ての次元の最初の要素番号が 0 と仮定
//  FFilePath : 保存先のファイル名
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LFHandle : THandle;
begin
  // 保存ファイル名のファイルを作成
  LFHandle := FileCreate(FFilePath);
  try
    FileWrite(LFHandle, FArray1[0][0][0], SizeOf(FArray1));
  finally
    // ファイルを閉じる
    FileClose(LFHandle);
  end;
end;

//=============================================================================
//  3 次元の静的配列のデータをファイルから読み込む
//
//  FArray1   : 3 次元の静的配列
//              要素のデータは Double 型と仮定
//              全ての次元の最初の要素番号が 0 と仮定
//  FFilePath : 保存先のファイル名
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LFHandle : THandle;
begin
  // ファイルを開く
  LFHandle := FileOpen(FFilePath, fmOpenRead);
  try
    // ファイルからデータを読み込む
    FileRead(LFHandle, FArray1[0][0][0], SizeOf(FArray1));
  finally
    // ファイルを閉じる
    FileClose(LFHandle);
  end;
end;