Delphi 2010:如何将整个记录保存到文件中?

发布于 2024-09-26 00:54:30 字数 171 浏览 2 评论 0原文

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

您有什么建议吗?

I have defined a record which has lots of fields with different types (integer, real , string, ... plus dynamic arrays in terms of "array of ...").
I want to save it as a whole to a file and then be able to load it back to my program. I don't want to go through saving each field's value individually.
The file type (binary or ascii or ...) is not important as long Delphi could read it back to a record.

Do you have any suggestions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

安静 2024-10-03 00:54:30

只要不使用动态数组,您就可以直接将记录的内存加载到流中或从流中保存记录的内存。因此,如果您使用字符串,则需要修复它们:

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

You can load and save the memory of a record directly to and from a stream, as long as you don't use dynamic arrays. So if you use strings, you need to make them fixed:

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

var 
  rTestRecord: TTestRecord;
  strm : TMemoryStream; 

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

You can even load or save an array of record at once!

type TRecordArray = array of TTestRecord;

var ra : TRecordArray; 

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

In case you want to write dynamic content:

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

After that, you can read it back:

strm.Read(iCount, Sizeof(iCount) );       //first read the length
SetLength(aArray, iCount);                //then alloc mem
strm.Read(aArray[0], SizeOf * iCount);    //then read content
自此以后,行同陌路 2024-10-03 00:54:30

正如这里所承诺的那样,它是: 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
请参阅自述文件以获取信息。

As promised here it is: https://github.com/KrystianBigaj/kblib

When you defined for example record as:

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;

You can save whole dynamic record to stream (as binary data) by :

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

To load it back:

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

It not need to be a record, you can do same for any dynamic type like:

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

Tested on Delphi 2006/2009/XE. License: MPL 1.1/GPL 2.0/LGPL 3.0
See readme for information.

天赋异禀 2024-10-03 00:54:30

另一个非常适合记录的选项 (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 动态数组对此进行了简要测试,并且存储和加载数组元素没有问题。

Another option which works very well for records (Delphi 2010+) is to use the SuperObject library. For example:

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;

I also tested this briefly with a simple TArray<Integer> dynamic array and it did not have a problem storing and loading the array elements.

多像笑话 2024-10-03 00:54:30

另一种解决方案,从 Delphi 5 到 XE,均可用 作为开源单元

事实上,它实现了:

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

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

我们在 ORM 中使用它,将动态数组属性等高级类型存储到数据库后端。迈向数据库分片架构的第一步。

Another solution, working from Delphi 5 up to XE, is available as an OpenSource unit.

In fact, it implements:

  • some low-level RTTI functions for handling record types: RecordEquals, RecordSave, RecordSaveLength, RecordLoad;
  • a dedicated TDynArray object, which is a wrapper around any dynamic array, able to expose TList-like methods around any dynamic array, even containing records, strings, or other dynamic arrays. It's able to serialize any dynamic array.

Serialization uses an optimized binary format, and is able to save and load any record or dynamic array as RawByteString.

We use this in our ORM, to store high-level types like dynamic array properties into a database back-end. First step to a DB-Sharding architecture.

寂寞美少年 2024-10-03 00:54:30

除了指示您如何执行此操作的答案之外,还请注意以下内容:

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

  2. 仅当您的记录不包含托管类型的字段时,您才能执行此操作。这意味着字段不能是以下托管类型:字符串、动态数组、变体和引用类型(例如 指针程序类型方法引用接口)和文件类型,或包含这些管理类型的类型。这基本上限制于这些非托管类型:

    • 答:简单类型(包括字节、整数、浮点数、枚举、字符等等)
    • B:短字符串
    • C:集合
    • D:A、B、C、D 和 E 的静态数组
    • E:A、B、C、D、E的记录

与其将记录写入文件或从文件中写入记录,不如使用类实例并进行转换它们与 JSON 相互转换,然后写入相当于文件的 JSON 字符串并将其读回。

您可以使用此单元为您进行 JSON 转换(应与 Delphi 2010 及更高版本一起使用;肯定适用于 Delphi XE 和向上)从 此位置

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.

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

——杰罗恩

In addition to the answers that indicate how you do this, please also be aware of these:

  1. You must be aware that writing records out to a file will be Delphi version specific (usually: specific to a series of Delphi versions that share the same memory layout for the underlying data types).

  2. You can only do that if your record does not contain fields of a managed type. Which means that fields cannot be of these managed types: strings, dynamic arrays, variants, and reference types (like pointers, procedural types, method references, interfaces or classes) and file types, or types that contain those manages types. Which basically limits to to these unmanaged types:

    • A: Simple types (including bytes, integers, floats, enumerations, chars and such)
    • B: Short strings
    • C: Sets
    • D: Static arrays of A, B, C, D and E
    • E: Records of A, B, C, D and E

In stead of writing out records to/from a file, it might be better to go with class instances and convert them to/from JSON, and them write the JSON string equivalent to a file and read it back in.

You can use this unit to do the JSON conversion for you (should work with Delphi 2010 and up; works for sure with Delphi XE and up) from this location this 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.

I hope this helps you getting an overview of things.

--jeroen

趴在窗边数星星i 2024-10-03 00:54:30

您还可以定义一个对象而不是记录,这样您就可以使用 RTTI 将对象保存为 XML 或其他格式。如果你有D2010或XE,你可以使用DeHL来序列化它:
Delphi 2010 DeHL 序列化 XML 和自定义属性:如何它有效吗?

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

You could also define an object instead of a record, so you can use RTTI to save your object to XML or whatever. If you have D2010 or XE, you can use DeHL to serialize it:
Delphi 2010 DeHL Serialization XML and custom attribute : how it work?

But if you "google" you can find other libs with RTTI and serialization (with D2007 etc)

捎一片雪花 2024-10-03 00:54:30

如果您有动态字符串或数组,则无法“整体”写入记录。我不会使用旧式的最多 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;

If you have dynamic strings or array you can't write the record "as a whole". Instead of using old style-25 characters max strings, I would add methods to the record to be able to "stream" itself to a stream, or better using a TFiler descendant:

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;
单挑你×的.吻 2024-10-03 00:54:30

来自 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;

Codes from 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;
故人爱我别走 2024-10-03 00:54:30

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

即使 Delphi 不存在 Embarcadero 提供的解决方案,也可以使用 Delphi 2010 中提供的扩展 RTTI 来实现。DeHL 库中提供了现成的实现 (这是一篇关于它的博客文章) - 但我不能透露太多关于实现的信息,我从未使用过 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;

The problem with saving a record containing dynamic array or real strings (or other "managed" types for that matter) is, it's not an big blob of memory containing everything, it's more like a tree. Someone or something needs to go over everything and save it to storage, somehow. Other languages (Python for example) include all sorts of facilities to transform most objects to text (serialize it), save it to disk and reload it (deserialize it).

Even though a Embarcadero-provided solution doesn't exist for Delphi, one can be implemented using the extended RTTI available in Delphi 2010. A ready-made implementation is available in the DeHL library (here's a blog post about it) - but I can't say much about the implementation, I never used DeHL.

An other option is the one you want to avoid: manually serialize the record to an TStream; It's actually not half difficult. Here's the kind of code I usually use to read/write objects to a file stream:

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;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文