Delphi Saving/Loading 动态数组失败

Delphi Saving/Loading Dynamic Arrays Failed

我认为这看起来像是“做我的作业”之类的问题,但我仍在“复制代码,使用它并尝试理解它'阶段,这是我所知道的发布该主题问题最活跃的事情。

我有一条记录:

type
  Card = record
    Name: string;
    Up,Right,Left,Down,Mark: Single; 
    IDNumber: Integer;
    end;

以及该记录的数组:

var
  ArrayCard: array of Card;

而且我想知道这种动态数组如何成为 stored/loaded to/from 文件。

尝试使用这段代码:http://www.pascalgamedevelopment.com/showthread.php?6319-save-load-a-dynamic-array 像这样:

Procedure TMainFrom.WriteMyData;

  Var
    FS : TFileStream;
    I,iSize : Integer;
    TmpPath: string;

  Begin
    TmpPath:= TPath.Combine(TPath.GetDocumentsPath, 'Cards.dat');
    FS := TFileStream.Create(TmpPath, fmOpenWrite);
    iSize:= Length(ArrayCard);
    FS.WriteBuffer(iSize,SizeOf(iSize));
  For I := 0 To iSize - 1 Do
    FS.WriteBuffer(ArrayCard[I],SizeOf(Card));
  FS.Free;
  End;

到目前为止它似乎工作正常,但后来我尝试像这样加载它:

Procedure TMainFrom.ReadMyData;

  Var
    FS : TFileStream;
    I,iSize : Integer;
    TmpPath: string;
    TempCard : Card;

  Begin
    TmpPath:= TPath.Combine(TPath.GetDocumentsPath, 'Cards.dat');
    FS := TFileStream.Create(TmpPath, fmOpenRead);
    FS.ReadBuffer(iSize,SizeOf(iSize));
    SetLength(ArrayCard,iSize);
  For I := 0 To iSize - 1 Do
    Begin
    FS.ReadBuffer(TempCard,SizeOf(Card));
    ArrayCard[I] := TempCard; //It Breaks Here...The Watch List: TempCard Inaccessible value
    End;
  FS.Free;
  End;

然后我在模块中得到一个异常 EAccessViolation...

然后我也尝试了这样的事情:delphi Save and Load dynamic array 它用正确数量的项目加载数组,但它们都是空的或空白的:

procedure TMainFrom.SaveCardsToFile;
var
  Stream: TStream;
  L: Integer;
  TmpPath: string;
  ICount: Integer;
  strm: TMemoryStream;

begin
  TmpPath:= TPath.Combine(TPath.GetDocumentsPath, 'Cards.bin');

  Stream:= TFileStream.Create(TmpPath, fmCreate);
  L:= Length(ArrayCard);
  Stream.WriteBuffer(L, SizeOf(L));
  Stream.WriteBuffer(Pointer(ArrayCard)^, L * SizeOf(Card));
  Stream.Free;
end;

procedure TMainFrom.LoadCardsFromFile;
var
  Stream: TStream;
  L: LongWord;
  TmpPath: string;

begin
  TmpPath:= TPath.Combine(TPath.GetDocumentsPath, 'Cards.bin');

  Stream:= TFileStream.Create(TmpPath, fmOpenRead);
  Stream.ReadBuffer(L, SizeOf(L));
  SetLength(ArrayCard, L);
  Stream.ReadBuffer(Pointer(ArrayCard)^, L * SizeOf(Card));
  Stream.Free;
end;

不要对包含 "normal" 个字符串的记录使用缓冲区操作。 shortstring 而不是 string 只是指向字符串内容的指针。因此,您的代码仅保存和加载指向字符串内容的指针,而不是字符串。如果加载的值指向无法访问的内存,您可能会遇到访问冲突。 更改您的代码以在记录中单独保存和加载变量,如下所示:

type
  TmyRec = record
    str: string;
    i: integer;
    procedure SaveToStream(Stream: TStream);
    procedure LoadFromStream(Stream: TStream);
  end;

{ myRec }

procedure TmyRec.LoadFromStream(Stream: TStream);
var
  strLen: integer;
  strBuf: TBytes;
begin
  Stream.Read(strLen, SizeOf(Integer));
  SetLength(strBuf, strLen);
  Stream.Read(strBuf, strLen);

  str:=TEncoding.UTF8.GetString(strBuf);

  Stream.Read(i, SizeOf(Integer));
end;

procedure TmyRec.SaveToStream(Stream: TStream);
var
  strBuf: TBytes;
  strLen: integer;
begin
  // direct set encoding type helps to avoid problems with different platforms.
  // for example, Windows uses UCS2, Android and iOS - UTF8 encoding
  strBuf:=TEncoding.UTF8.GetBytes(str);
  strLen:=Length(strBuf);

  Stream.Write(strLen, SizeOf(Integer));
  Stream.Write(strBuf, strLen);
  Stream.Write(i, SizeOf(Integer));
end;

更新: 你读过泛型吗?您可以使用 TList 和其中的 load/save 条记录代替动态数组:

type
  TmyRecList = class(TList<TmyRec>)
  public
    procedure SaveToStream(Stream: TStream);
    procedure LoadFromStream(Stream: TStream);
  end;

{ TmyRecList }

procedure TmyRecList.LoadFromStream(Stream: TStream);
var
  Len: integer;
  i: Integer;
  rec: TmyRec;
begin
  Clear;

  Stream.Read(Len, SizeOf(integer));

  for i := 0 to Len-1 do
    begin
      Rec.LoadFromStream(Stream);
      Add(rec);
    end;
end;

procedure TmyRecList.SaveToStream(Stream: TStream);
var
  i: Integer;
begin
  Stream.Write(Count, SizeOf(Integer));
  for i := 0 to Count-1 do
    Items[i].SaveToStream(Stream);
end;

procedure THeaderFooterForm.FormCreate(Sender: TObject);
var
  Stream: TStream;
  rec: TmyRec;
  recList: TmyRecList;
begin
  Stream:=TMemoryStream.Create;
  try
    recList:=TmyRecList.Create;
    try
      rec.str:='sample text';
      rec.i:=123;
      recList.Add(rec);

      rec.str:='another text';
      rec.i:=234;
      recList.Add(rec);

      recList.SaveToStream(Stream);
      Stream.Seek(0, soBeginning);

      recList.LoadFromStream(Stream);
      ShowMessage('this is str value in second record: ' + recList[1].str);
    finally
      recList.Free;
    end;
  finally
    Stream.Free;
  end;

在一些帮助下,我成功地重新编写了我的代码以使其正常工作。 首先,我需要使 string 类似于 string[20],但无法针对 android 进行编译,因此我修改了我的记录以使用 array of char,如下所示:

type
  EventString= array [0..20] of Char;

  Card = record
    Name:  EventString; //string[20]
    Up,Right,Left,Down,Mark: Single;
    IDNumber: Integer;
    end; 

然后我修改了 Saving/Loading 程序以使用 TFileStream 而不是 TStream 和 for 循环:

procedure TMainFrom.SaveCardsToFile;
var
  Stream: TFileStream;
  L: Integer;
  TmpPath: string;
  n: Integer;

begin
  TmpPath:= TPath.Combine(TPath.GetDocumentsPath, 'Cards.bin');
  Stream:= TFileStream.Create(TmpPath, fmCreate);
  L:= Length(ArrayCard)-1;
  for n:=0 to L do
  begin
    Stream.WriteBuffer(ArrayCard[n], SizeOf(Card)); //Saving
  end;
  Stream.Free;
end;

procedure TMainFrom.LoadCardsFromFile;
var
  Stream: TFileStream;
  L: LongWord;
  TmpPath: string;
  n: Integer;

begin
  TmpPath:= TPath.Combine(TPath.GetDocumentsPath, 'Cards.bin');
  Stream:= TFileStream.Create(TmpPath, fmOpenRead);
  SetLength(ArrayCard, Round(Stream.Size/SizeOf(Card)));
  L:= Length(ArrayCard)-1;
  for n:=0 to L do
  begin
    Stream.ReadBuffer(ArrayCard[n], SizeOf(Card)); //Loading
    ListCard.Items.Add(ArrayCard[n].Name); //Just adds card names to a listbox
  end;
  Stream.Free;

end;

现在一切正常。