如何在 Delphi 中将动态数组保存到 FileStream?

7

我在 Delphi 2009 中有以下的结构:

type
  IndiReportIndi = record
    IndiName: string;
    NameNum: integer;
    ReportIndiName: string;
  end;

var
  XRefList: array of IndiReportIndi;

这里的XRefList是一个动态数组。

我想将XRefList保存到一个FileStream中。我该如何做,并且包括所有IndiName和ReportIndiName字符串,以便在以后从该FileStream加载时可以检索它们?


1
自2010年12月以来,有一个新的开源单元值得考虑,用于序列化记录或动态数组(比序列化具有更多功能)-适用于Delphi 5到XE2。 - Arnaud Bouchez
@ArnaudBouchez 这个链接已经失效了。 - SHIN JaeGuk
正确的链接现在是https://blog.synopse.info/?post/2011/03/12/TDynArray-and-Record-compare/load/save-using-fast-RTTI - 可以与最新的Delphi一起使用,也可以与FPC/Lazarus一起使用。 - Arnaud Bouchez
5个回答

10
type
  IndiReportIndi = record
    IndiName: string;
    NameNum: integer;
    ReportIndiName: string;
    procedure SaveToStream(Stream: TStream);
    procedure LoadFromStream(Stream: TStream);
  end;

type
  TXRefList = array of IndiReportIndi;

function LoadString(Stream: TStream): string;
var
  N: Integer;

begin
  Result:= '';
  Stream.ReadBuffer(N, SizeOf(Integer));
  if N > 0 then begin
    SetLength(Result, N);
//    Stream.ReadBuffer(Result[1], N * SizeOf(Char));
// fast version - see comment by A.Bouchez
    Stream.ReadBuffer(Pointer(Result)^, N * SizeOf(Char));
  end;
end;

procedure SaveString(Stream: TStream; const S: string);
var
  N: Integer;

begin
  N:= Length(S);
  Stream.WriteBuffer(N, SizeOf(Integer));
  if N > 0 then
//    Stream.WriteBuffer(S[1], N * SizeOf(Char));
// fast version - see comment by A.Bouchez
    Stream.WriteBuffer(Pointer(S)^, N * SizeOf(Char));
end;

procedure IndiReportIndi.LoadFromStream(Stream: TStream);
var
  S: string;

begin
  IndiName:= LoadString(Stream);
  Stream.ReadBuffer(NameNum, SizeOf(Integer));
  ReportIndiName:= LoadString(Stream);
end;

procedure IndiReportIndi.SaveToStream(Stream: TStream);
begin
  SaveString(Stream, IndiName);
  Stream.WriteBuffer(NameNum, SizeOf(Integer));
  SaveString(Stream, ReportIndiName);
end;

function LoadXRefList(Stream: TStream): TXRefList;
var
  N: Integer;
  I: Integer;

begin
  Stream.ReadBuffer(N, SizeOf(Integer));
  if N <= 0 then Result:= nil
  else begin
    SetLength(Result, N);
    for I:= 0 to N - 1 do
      Result[I].LoadFromStream(Stream);
  end;
end;

procedure SaveXRefList(Stream: TStream; const List: TXRefList);
var
  N: Integer;
  I: Integer;

begin
  N:= Length(List);
  Stream.WriteBuffer(N, SizeOf(Integer));
  for I:= 0 to N - 1 do
    List[I].SaveToStream(Stream);
end;

2
在LoadString中,你本可以使用指针(Result)^代替Result[1],在SaveString中,你本可以使用指针(S)^代替S[1],因为这样可以避免调用UniqueString()。如果在所有情况下都使用result := ''来编写LoadString,那么它可能会更快一些,然后如果N>0,则使用SetLength(Result,N):这样就不会进行内存重新分配(使用缓慢的move操作),而是进行字符串的终结和新的分配(这样更快)。 - Arnaud Bouchez
使用TReader/TWriter可以简化标准数据类型的编写。相比于Stream.WriteBuffer(N, SizeOf(Integer));,你只需要写Write.WriteInger(N)和N := Reader.ReadInteger;。它们还支持字符串和变量,并且代码更易读。你只是在重复Delphi已经有的代码。 - user160694
@ldsandon:TReader/TWriter的实现取决于Delphi版本,可能会发生变化。依赖TReader/TWriter方法来实现自己的数据序列化是不可取的。 - kludg
1
OP记录结构也会发生变化 - 你的代码也取决于Delphi的版本。如果该文件应该在使用不同版本的Delphi编译的应用程序之间使用,则应添加一个包含版本信息的头部,以允许读取应用程序使用正确的读取函数。 - user160694

6
var
  S: TStream;
  W: TWriter;
  A: array of IndiReportIndi;
  E: IndiReportIndi;
  ...
begin
  S := nil;
  W := nil;
  try 
    S := TFileStream.Create(...);
    W := TWriter.Create(S);
    W.WriteInteger(Length(A));
    for E in A do
    begin
      W.WriteString(E.IndiName);
      W.WriteInteger(E.NameNum);
      W.WriteString(E.ReportIndiName);
    end;
  finally
    W.Free;
    S.Free
  end;
end;  

为了读取这些数据,您需要使用TReader和ReadInteger/ReadString。

谢谢 ldsandon。漂亮、简洁、简单。+1。 - lkessler
WTF?Idsadon的账户被删除了吗?他在SO上是一个杰出的用户。 - Gabriel

6
使用:http://code.google.com/p/kblib/
type
  IndiReportIndi = record
    IndiName: string;
    NameNum: integer;
    ReportIndiName: string;
  end;

  TXRefList = array of IndiReportIndi;

var
  XRefList: TXRefList;

如果要将整个XRefList保存到流中,请使用以下命令:

TKBDynamic.WriteTo(lStream, XRefList, TypeInfo(TXRefList));

加载它回来:

TKBDynamic.ReadFrom(lStream, XRefList, TypeInfo(TXRefList));

看起来是一个非常好的例程。感谢指出。 - lkessler
不错的库。[但是]如果你这样做,那么,如果你更改了记录(比如说你添加了另一个字段到你的记录中),你就不能再加载保存在原始记录中的旧文件了。我只会在保证你的记录永远不会改变的情况下使用它!但这种保证,在现实生活中很少可能出现。 - Gabriel

1

不知道 EX 函数,我得去了解一下。 - lkessler

-1

为了确保答案准确:

考虑查看TDynArray包装器,它能够将任何记录序列化为二进制,并且可以在动态数组之间进行转换。

有很多类似于TList的方法,包括新方法(如哈希或二进制搜索)。

非常优化速度和磁盘使用,适用于Delphi 5到XE2 - 并且是开源的。

另请参见如何在TList中存储动态数组?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接