Delphi 2010: как сохранить всю запись в файл? - PullRequest
19 голосов
/ 29 сентября 2010

Я определил запись, которая имеет множество полей с разными типами (целые, вещественные, строковые, ... плюс динамические массивы в терминах "массив ...") Я хочу сохранить его целиком в файл и затем загрузить его обратно в мою программу. Я не хочу сохранять значение каждого поля отдельно. Тип файла (двоичный или ascii или ...) не важен, поскольку Delphi может читать его обратно в запись.

У вас есть предложения?

Ответы [ 9 ]

20 голосов
/ 29 сентября 2010

Вы можете загружать и сохранять память записи непосредственно в и из потока, если вы не используете динамические массивы. Поэтому, если вы используете строки, вам нужно исправить их:

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
11 голосов
/ 02 октября 2010

Как и было обещано здесь: 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 Смотрите readme для информации.

4 голосов
/ 30 сентября 2010

Другой вариант, который очень хорошо работает с записями (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<Integer> динамическим массивом, и у него не было проблем с сохранением и загрузкой элементов массива.

4 голосов
/ 29 сентября 2010

В дополнение к ответам, которые указывают, как вы это делаете, учтите также следующее:

  1. Вы должны знать, что запись записей в файл будет зависеть от версии Delphi (обычно: от серии версий Delphi, которые используют одну и ту же структуру памяти для базовых типов данных).

  2. Это можно сделать только в том случае, если ваша запись не содержит полей управляемого типа. Это означает, что поля не могут быть следующих управляемых типов: строки , динамические массивы, варианты и ссылочные типы (например, указатели , процедурные типы , ссылки на методы , интерфейсы или классы ) и типы файлов или типы, которые содержат эти типы управления. Что в основном ограничивается этими неуправляемыми типами:

    • A: Простые типы (включая байты, целые числа, числа с плавающей запятой, перечисления, символы и т. Д.)
    • 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.

Надеюсь, это поможет вам получить представление о вещах.

- Йерун

3 голосов
/ 26 июля 2011

Другое решение, работающее от Delphi 5 до XE, доступно как модуль OpenSource .

На самом деле, он реализует:

  • некоторые функции RTTI низкого уровня для обработки типов записей: RecordEquals, RecordSave, RecordSaveLength, RecordLoad;
  • выделенный TDynArray объект, который является оберткой вокруг любого динамического массива, способный предоставлять TList-подобные методы вокруг любого динамического массива, даже содержащего записи, строки или другие динамические массивы. Он может сериализовать любой динамический массив.

Сериализация использует оптимизированный двоичный формат и может сохранять и загружать любую запись или динамический массив как RawByteString.

Мы используем это в нашем ORM для хранения высокоуровневых типов, таких как свойства динамического массива, в серверной части базы данных. Первый шаг к архитектуре DB-Sharding .

3 голосов
/ 29 сентября 2010

Вы также можете определить объект вместо записи, так что вы можете использовать RTTI для сохранения вашего объекта в XML или что-то еще.Если у вас D2010 или XE, вы можете использовать DeHL для его сериализации: Delphi 2010 DeHL Serialization XML и пользовательский атрибут: как это работает?

Но если вы «Google», вы можете найти другиеlibs с RTTI и сериализацией (с D2007 и т. д.)

2 голосов
/ 29 сентября 2010

Если у вас есть динамические строки или массив, вы не можете записать запись «целиком». Вместо того, чтобы использовать максимальные строки из старого стиля в 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;
0 голосов
/ 29 сентября 2010

Проблема с сохранением записи, содержащей динамический массив или реальные строки (или другие «управляемые» типы в этом отношении), состоит в том, что это не большой блок памяти, содержащий все, это больше похоже на дерево. Кто-то или что-то должно пройти через все и как-то сохранить это в хранилище. Другие языки (например, Python) включают в себя всевозможные средства для преобразования большинства объектов в текст (сериализации), сохранения на диск и перезагрузки (десериализации).

Даже несмотря на то, что решение для Delphi, предоставляемое Embarcadero, не может быть реализовано, можно использовать расширенный RTTI, доступный в Delphi 2010. Готовая реализация доступна в библиотеке 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;
0 голосов
/ 29 сентября 2010

Коды от 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;
...