【问题标题】:Delphi 2010: How to save a whole record to a file?Delphi 2010:如何将整条记录保存到文件中?
【发布时间】:2010-09-29 11:06:08
【问题描述】:

我已经定义了一个记录,其中包含许多不同类型的字段(整数、实数、字符串、...加上“...的数组”方面的动态数组)。 我想将它作为一个整体保存到一个文件中,然后能够将它加载回我的程序。我不想单独保存每个字段的值。 文件类型(二进制或 ascii 或 ...)并不重要,只要 Delphi 可以将其读回记录即可。

你有什么建议吗?

【问题讨论】:

  • delphi.about.com/od/fileio/a/fileof_delphi.htm 还要确保使用packed record,因为常规记录的内存对齐方式在版本之间可能会发生变化
  • 一个新的OpenSource unit and classes 值得考虑用于序列化记录或动态数组(具有比序列化更多的功能) - 适用于 Delphi 5 到 XE2。自 Delphi 2009 以来,恕我直言,使用短字符串不是一个选项,因为这些字符串是 Ansi 字符串 - 你会丢失很多文件空间。
  • 为什么不单独保存每个字段?反正也没那么难。向您的记录过程 MyRec.WriteToStream(DiskStream: TStream) 添加一个方法。在该方法中,将每个记录字段保存到流中。您的代码不是独立的,而不是使用 3rd 方库。而且您只花费了几行额外的代码(每个字段大约一行)。是不是很糟糕!这里推荐的一些库已经不存在了(超级对象)。

标签: delphi file-io delphi-2010


【解决方案1】:

只要不使用动态数组,您就可以直接从流中加载和保存记录的内存。所以如果你使用字符串,你需要将它们固定:

type TTestRecord = record 
  FMyString : string[20]; 
end; 

var 
  rTestRecord: TTestRecord;
  strm : TMemoryStream; 

strm.Write(rTestRecord, Sizeof(TTestRecord) );

您甚至可以一次加载或保存一组记录!

type TRecordArray = array of TTestRecord;

var ra : TRecordArray; 

strm.Write(ra[0], SizeOf(TTestRecord) * Length(ra));

如果你想写动态内容:

iCount   := Length(aArray);
strm.Write(iCount, Sizeof(iCount) );      //first write our length
strm.Write(aArray[0], SizeOf * iCount);   //then write content

之后,你可以回读:

strm.Read(iCount, Sizeof(iCount) );       //first read the length
SetLength(aArray, iCount);                //then alloc mem
strm.Read(aArray[0], SizeOf * iCount);    //then read content

【讨论】:

  • 很好的解决方案,但遗憾的是我也有动态数组。我能做些什么?有没有办法可以将记录的“当前”状态保存到流中?
  • 记录/dyn.arrays/strings 有足够的 RTTI 信息来保存任何动态记录/数组/字符串/等。具有一个功能。在 D2009 中测试。包含例如动态记录的动态数组的记录......也不是问题。分析 System.pas:_FinalizeArray
  • 记录必须打包才能这样保存!
【解决方案2】:

这里承诺的是:https://github.com/KrystianBigaj/kblib

当你定义例如记录为:

TTestRecord = record
  I: Integer;
  D: Double;
  U: UnicodeString;
  W: WideString;
  A: AnsiString;
  Options: TKBDynamicOptions;

  IA: array[0..2] of Integer;

  AI: TIntegerDynArray;
  AD: TDoubleDynArray;
  AU: array of UnicodeString;
  AW: TWideStringDynArray;
  AA: array of AnsiString;

  R: array of TTestRecord; // record contain dynamic array of itself (D2009+)
end;

您可以通过以下方式将整个动态记录保存到流(作为二进制数据):

TKBDynamic.WriteTo(lStream, lTestRecord, TypeInfo(TTestRecord));

要加载它:

TKBDynamic.ReadFrom(lStream, lTestRecord, TypeInfo(TTestRecord));

它不需要是记录,你可以对任何动态类型做同样的事情,比如:

TKBDynamic.WriteTo(lStream, lStr, TypeInfo(UnicodeString));
TKBDynamic.WriteTo(lStream, lInts, TypeInfo(TIntegerDynArray));
TKBDynamic.WriteTo(lStream, lArrayOfTestRecord, TypeInfo(TArrayOfTestRecord)); // TArrayOfTestRecord = array of TTestRecord;

在 Delphi 2006/2009/XE 上测试。许可证:MPL 1.1/GPL 2.0/LGPL 3.0 有关信息,请参阅自述文件。

【讨论】:

  • +1 这很好用。通过使用 NativeUInt 而不是 cardinal 进行指针运算,可以使代码与即将推出的 64 位编译器兼容。
  • @Arnaud Bouchez - 添加了 x64 编译器支持(有关 x86 和 x64 之间二进制数据兼容性的详细信息,请参阅自述文件)
  • 不错!也感谢项目维护。
  • 完美 -> 使用 uKBDynamic;
【解决方案3】:

另一个非常适合记录的选项(Delphi 2010+)是使用SuperObject 库。例如:

type
  TData = record
    str: string;
    int: Integer;
    bool: Boolean;
    flt: Double;
  end;
var
  ctx: TSuperRttiContext;
  data: TData;
  obj: ISuperObject;
  sValue : string;
begin
  ctx := TSuperRttiContext.Create;
  try
    sValue := '{str: "foo", int: 123, bool: true, flt: 1.23}';
    data := ctx.AsType<TData>(SO(sValue));
    obj := ctx.AsJson<TData>(data);
    sValue := Obj.AsJson;
  finally
    ctx.Free;
  end;
end;

我还用一个简单的TArray&lt;Integer&gt; 动态数组对此进行了简单的测试,它在存储和加载数组元素时没有问题。

【讨论】:

  • +1 似乎是一个不错的解决方案(尽管作为菜鸟,这有点难以理解!)。但我有 3 个问题:1)如何从现有的大记录中生成 sValue 以将其传递给“SO()”? 2)这里要存储到文件中的对象是什么? sValue 还是 obj? 3)如何将其加载回记录?再次感谢您的回答。
  • sValue 将记录表示为 JSON 数据包。您可以轻松地将此单个字符串保存到文本文件中。在上面的示例中,两行“Obj := ctx.AsJson(data)”和“sValue := Obj.AsJson”执行从记录到字符串的神奇转换。存储 sValue。上一行“data := ctx.AsType(so(sValue));”是什么解析这个并用适当的值填充记录数据。
【解决方案4】:

除了说明您如何执行此操作的答案外,还请注意以下事项:

  1. 您必须注意,将记录写入文件将是特定于 Delphi 版本的(通常:特定于为底层数据类型共享相同内存布局的一系列 Delphi 版本)。

  2. 只有当您的记录不包含托管类型的字段时,您才能这样做。这意味着字段不能属于以下托管类型:strings、动态数组、variants 和引用类型(如 pointersprocedural typesmethod referencesinterfacesclasses)和文件类型,或包含这些管理类型的类型。基本上仅限于这些非托管类型:

    • 答:Simple types(包括字节、整数、浮点数、枚举、字符等)
    • 乙:Short strings
    • C:套装
    • D:A、B、C、D 和 E 的静态数组
    • E:A、B、C、D 和 E 的记录

与其将记录写入文件或从文件中写入记录,不如使用类实例并将它们转换为 JSON 或从 JSON 转换它们,然后它们写入与文件等效的 JSON 字符串并将其读回。

您可以使用这个单元为您进行 JSON 转换(应该适用于 Delphi 2010 及更高版本;肯定适用于 Delphi XE 及更高版本)来自this locationthis location

unit BaseObject;

interface

uses DBXJSON, DBXJSONReflect;

type
  TBaseObject = class
  public
    { public declarations }
    class function ObjectToJSON<T : class>(myObject: T): TJSONValue;
    class function JSONToObject<T : class>(json: TJSONValue): T;
  end;

implementation

{ TBaseObject }

class function TBaseObject.JSONToObject<T>(json: TJSONValue): T;
var
  unm: TJSONUnMarshal;
begin
  if json is TJSONNull then
    exit(nil);
  unm := TJSONUnMarshal.Create;
  try
    exit(T(unm.Unmarshal(json)))
  finally
    unm.Free;
  end;

end;

class function TBaseObject.ObjectToJSON<T>(myObject: T): TJSONValue;
var
  m: TJSONMarshal;
begin

  if Assigned(myObject) then
  begin
    m := TJSONMarshal.Create(TJSONConverter.Create);
    try
      exit(m.Marshal(myObject));
    finally
      m.Free;
    end;
  end
  else
    exit(TJSONNull.Create);

end;

end.

我希望这可以帮助您了解事物的概况。

--杰罗恩

【讨论】:

    【解决方案5】:

    您还可以定义一个对象而不是记录,因此您可以使用 RTTI 将您的对象保存为 XML 或其他格式。如果你有 D2010 或 XE,你可以使用 DeHL 对其进行序列化: Delphi 2010 DeHL Serialization XML and custom attribute : how it work?

    但是如果你“谷歌”你可以找到其他带有 RTTI 和序列化的库(使用 D2007 等)

    【讨论】:

      【解决方案6】:

      另一个解决方案,从 Delphi 5 到 XE,可用as an OpenSource unit

      其实它实现了:

      • 一些用于处理记录类型的低级 RTTI 函数:RecordEquals, RecordSave, RecordSaveLength, RecordLoad;
      • 专用的TDynArray 对象,它是任何动态数组的包装器,能够在任何动态数组周围公开类似TList 的方法,甚至包含记录、字符串或其他动态数组。它能够序列化任何动态数组。

      序列化使用优化的二进制格式,可以将任何记录或动态数组保存和加载为RawByteString

      我们在 ORM 中使用它,将动态数组属性等高级类型存储到数据库后端。第一步到DB-Sharding architecture

      【讨论】:

      • +1,这似乎是一个很好的解决方案。我一定会试试的。非常感谢您提及。
      • 它是 SQLite 的一部分还是可以作为一个独立的单元添加到项目中?它可以保存/加载到文件吗?
      • @Flom 它被我们使用 SQLite 的 ORM 使用,但它不是其中的一部分。它可用于保存/加载到文件,即使在必要时压缩后也是如此。 SynCommons.pas 单元是自包含的(事实上,它确实链接到 Synopse.inc 和 SynLZ.pas)并且不需要 SQLite 或我们库的任何其他部分。您在本单元中还有一些不错的其他低级功能,例如 UTF-8 编码、JSON 序列化和增强的日志记录 - 带有堆栈跟踪和异常跟踪。享受开源!
      【解决方案7】:

      如果您有动态字符串或数组,则不能“作为一个整体”写入记录。我不会使用旧式最多 25 个字符的字符串,而是向记录添加方法,以便能够将自身“流式传输”到流中,或者更好地使用 TFiler 后代:

      TMyRec = record
        A: string;
        B: Integer;
        procedure Read(AReader: TReader);
        procedure Writer(AWriter: TWriter);
      end;
      
      procedure TMyrec.Read(AReader: TReader);
      begin
        A := AReader.ReadString;
        B := AReader.ReadInteger;
      end;
      

      【讨论】:

      • 不要撒谎;)您可以编写整个动态记录,但您需要编写/查找对任何记录/dyn.array/string 执行此操作的函数(不仅仅是像上面那样的特定记录)。 Delphi RTL 没有自带,不知道为什么...我通过分析 System.pas:_Finalize 的工作原理构建了一个。使用大约 1k 行代码,我制作了适用于任何动态数组/字符串/记录的函数,如下所示:SizeOfDynamic(const ADynamicType; ATypeInfo: PTypeInfo)、WriteDynamicToStream、ReadDynamicFromStream、CompareDynamic。用法就是 WriteDynamicToStream/ReadDynamicFromStream(lFileStream, lMyRec, TypeInfo(TMyRec))。
      • 你可以写整个动态数据,但是你不能写一个字段是动态的记录,只写记录内存映像。无论如何,您必须单独阅读它们并单独存储它们。走什么路取决于你的需要。您可以使用临时解决方案或通用解决方案 - 这可能需要更多时间来开发。记录了 RTTI、动态数组和字符串的存储方式,您不需要阅读太多 system.pas。只有“1k”行代码?有整个应用程序用更少的行编写。无论如何,展示你的答案,而不是仅仅批评别人......
      • +1,因为即使是那些 OP 明确要求的方法也不是 go through saving each field's value individually,这种方法值得考虑:与任何基于 RTTI 的方法相比,您可以获得空间和时间效率,并且您得到在处理记录结构的未来变化方面具有很大的灵活性。
      • 等这个周末,你会得到这个单元的链接(需要再写一个,因为之前,我在工作中写过,所以你应该明白我为什么没有发布'之前的链接)。这当然是免费的(MPL/GPL/LGPL 许可)。而且你也会得到一个答案,为什么我确实以这种“批评风格”写过以前的 cmets(不仅对你,对个人没有任何影响)。这个单元将非常适合这个 SO 问题。 PS。它将是一个二进制存储。
      • 也处理这个:PMyRec = ^TMyRec; TMyRec = 打包记录大小:Cardinal;缓冲区:字节数组[0..0];结尾; ... GetMem(MyRec, SizeOf(Cardinal) + 1024); MyRec^.Size := 1024; ....这种记录经常用在一些API调用中。
      【解决方案8】:

      来自delphibasics的代码:

       type
         TCustomer = Record
           name : string[20];
           age  : Integer;
           male : Boolean;
         end;
      
       var
         myFile   : File of TCustomer;  // A file of customer records
         customer : TCustomer;          // A customer record variable
      
       begin
         // Try to open the Test.cus binary file for writing to
         AssignFile(myFile, 'Test.cus');
         ReWrite(myFile);
      
         // Write a couple of customer records to the file
         customer.name := 'Fred Bloggs';
         customer.age  := 21;
         customer.male := true;
         Write(myFile, customer);
      
         customer.name := 'Jane Turner';
         customer.age  := 45;
         customer.male := false;
         Write(myFile, customer);
      
         // Close the file
         CloseFile(myFile);
      
         // Reopen the file in read only mode
         FileMode := fmOpenRead;
         Reset(myFile);
      
         // Display the file contents
         while not Eof(myFile) do
         begin
           Read(myFile, customer);
           if customer.male
           then ShowMessage('Man with name '+customer.name+
                            ' is '+IntToStr(customer.age))
           else ShowMessage('Lady with name '+customer.name+
                            ' is '+IntToStr(customer.age));
         end;
      
         // Close the file for the last time
         CloseFile(myFile);
       end;
      

      【讨论】:

      • 正如我上面提到的:我的记录中有动态数组。这不适用于“文件”。还有其他解决方案吗?
      【解决方案9】:

      保存包含动态数组或真实字符串(或其他“托管”类型)的记录的问题在于,它不是包含所有内容的大内存块,它更像是一棵树。有人或某事需要以某种方式检查所有内容并将其保存到存储中。其他语言(例如 Python)包括各种将大多数对象转换为文本(序列化)、保存到磁盘并重新加载(反序列化)的工具。

      尽管 Delphi 不存在 Embarcadero 提供的解决方案,但可以使用 Delphi 2010 中提供的扩展 RTTI 来实现。DeHL 库(here's a blog post about it) 中提供了现成的实现 - 但我不能多说实现,我没用过DeHL。

      另一个选项是您要避免的:手动将记录序列化为 TStream;这实际上并不难。以下是我通常用于将对象读/写到文件流的代码:

      procedure SaveToFile(FileName:string);
      var F:TFileStream;
          W:TWriter;
          i:Integer;
      begin
        F := TFileStream.Create(FileName, fmCreate);
        try
          W := TWriter.Create(F, 128);
          try
            // For every field that needs saving:
            W.WriteString(SomeStr);
            W.WriteInteger(TheNumber);
            // Dynamic arrays? Save the length first, then save
            // every item. The length is needed when reading.
            W.WriteInteger(Length(DArray));              
            for i:=0 to High(DArray) do
              W.WriteString(DArray[i]);
          finally W.Free;
          end;
        finally F.Free;
        end;
      end;
      
      procedure ReadFromFile(FileName:string);
      var F:TFileStream;
          R:TReader;
          i,n:Integer;
      begin
        F := TFileStream.Create(FileName, fmOpenRead);
        try
          R := TReader.Create(F, 128);
          try
            SomeStr := R.ReadString;
            TheNumber := R.ReadInteger;
            // Reading the dynamic-array. We first get the length:
            n := R.ReadInteger;
            SetLength(DArray, n);
            // And item-by-item
            for i:=0 to n-1 do
              DArray[i] := R.ReadString;
          finally R.Free;
          end;    
        finally F.Free;
        end;
      end;
      

      【讨论】:

      • "... Delphi 不存在解决方案..." 但您可以自己制作一个,不仅适用于 D2010,还适用于任何 Delphi (win-native) 版本(没有试过.NET)。从这个“主题”中阅读我的 cmets
      • @kibab,您似乎想卖东西,但您忘记提供 1k-wander 的链接;当你引用我的时候,你介意引用整个短语,而不仅仅是符合你目的的 5 个词吗?整句话是:`即使是 Embarcadero 提供的那些 Delphi 不存在的解决方案,也可以使用 Delphi 2010 中提供的扩展 RTTI 来实现`。你有没有注意到我发布了一个指向 DeHL 的链接,对吧?而且您确实知道并非所有东西都能获得 RTTI,即使使用 Delphi 2010 也不行?示例:枚举类型中的元素没有 RTTI。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-11
      • 1970-01-01
      • 2017-08-08
      • 2014-01-31
      • 2015-03-21
      • 2021-07-10
      相关资源
      最近更新 更多