代码之家  ›  专栏  ›  技术社区  ›  Vepir

Delphi保存/加载动态数组失败

  •  3
  • Vepir  · 技术社区  · 8 年前

    我想这看起来像‘ 做我的家庭作业 “这是一个问题,但我仍然在” 复制代码,使用它并尝试理解它 ’阶段,这是我所知道的最活跃的发布这个主题问题的事情。

    我有一个记录:

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

    以及该记录的数组:

    var
      ArrayCard: array of Card;
    

    我想知道如何将这种动态数组存储/加载到文件中。

    尝试使用这段代码: 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;
    
    2 回复  |  直到 7 年前
        1
  •  5
  •   kami    8 年前

    不要对包含“正常”字符串的记录使用缓冲区操作。而不是 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并在其中加载/保存记录:

    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;
    
        2
  •  1
  •   Vepir    8 年前

    在一些帮助下,我成功地重新编写了代码,使其能够正常工作。 首先,我需要 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; 
    

    然后我修改了保存/加载过程,以使用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;
    

    现在它工作得很好。