【问题标题】:Reading/writing dynamic arrays of objects to a file - Delphi将对象的动态数组读/写到文件 - Delphi
【发布时间】:2013-06-17 17:17:12
【问题描述】:

我正在尝试编写一些代码,将动态对象数组读/写到文件中。这些对象表示 java 源代码的结构。我需要能够扫描整个源代码并收集有关字段、方法和类的信息。我有一个算法可以做到这一点,结果保存在 TFieldStruc、TMethodStruc 和 TClassStruc 的结构中,它们都是 TCoreStruc 的后代(TObject 的后代)。 Java 源代码需要几分钟才能被扫描并生成虚拟结构。因此,我的应用程序会扫描所有源代码一次并将其保存为更易于访问的格式,该格式会在 IDE 启动时加载。

有没有办法(除了将对象“导出为字符串”,然后在加载时重新创建它们)将 TFieldStruc、TMethodStruc 和 TClassStruc 的整个三个数组流式传输到文件中,以便可以读取它们稍后?

我尝试读取和写入“TFieldStruc 文件..”和 TFileStream 以将对象保存到文件并读回,但在这两种情况下,我在调试器中都得到“不可访问的值”,然后是“再次访问对象时出现“访问冲突”错误。如果有人对如何解决这个问题有想法,将不胜感激。以下是 TCodeStruc 的代码,如果它的任何字段/方法可能导致问题:

type
  TCoreStruc = class(TObject)
    public
      LowerPointer : integer;
      HigherPointer : integer;
      Line : integer;
      Word : integer;
      Char : integer;
      CoreType : ansistring;
      IsPublic : boolean;
      IsPrivate : boolean;
      IsStatic : boolean;
      IsFinal : boolean;
      Name : ansistring;
      NestedStruc : TCoreStruc;
      constructor Create(Name, CoreType : ansistring; NestedStruc : TCoreStruc; IsPublic, IsPrivate, IsStatic, IsFinal : boolean);
      procedure UpdateValues(NestedStruc : TCoreStruc; IsPublic, IsPrivate, IsStatic, IsFinal : boolean);
      procedure SetPosition(Line, Word, Char : integer);
  end;

【问题讨论】:

标签: delphi


【解决方案1】:

这是一个使用您的结构的示例。

关于这个的几点说明:

  • 有很多不同的方法可以解决这个问题。听从 David Heffernan 的建议,对序列化进行一些搜索。我在我的一个应用程序中使用了以下方法,但其他方法包括使用 RTTI/Persistent 对象来迭代对象的已发布属性。有些库会为您迭代对象并完成所有工作。

  • 您需要将动态对象的大小实际写入字符串。这包括数组和字符串之类的东西。

  • 在我的示例中,每个对象都知道如何读写自己到流中。

  • 我对对象的固定长度部分使用结构。这使我无需单独编写每个数据元素。

  • 字符串需要单独写入以包含它们的大小(您可以使用像 Delphi 短字符串这样的固定长度缓冲区,但写出常规字符串的工作量并不大。您需要确定什么类型您希望写入字符串数据的格式。我为我的应用程序选择了 UTF8。

  • 对于您的其他数组,您可以在写出第一个数组后写入它们的数据(包括长度)。有时会有一个标题部分,其中包括顶部所有动态部分的长度,其他人将在结构开始之前写入长度写入。关键部分是始终以相同的顺序编写内容并包含在可以重新读取的位置。

  • 下面的文件结构中没有错误检查或验证。如果读取和写入之间有任何不同,它就会崩溃 - 可能会出现流读取错误。

  • 对结构的任何更改都会导致无法正确读取旧文件。有多种方法可以对文件进行版本控制,以确保您仍然可以阅读旧格式。这里不包括。

  • 在您的应用程序中,您可以将 TFileStream 传递给读写函数。我喜欢只用 TStream 编写实际的读/写函数。然后对象不关心数据来自哪里。它可能是文件,也可能已经在内存中。

  • 如果将以下单元拖放到控制台应用程序中,您应该能够添加对 Main 的调用并逐步执行示例。

单元核心结构; 界面 使用类、类型; 类型 TCoreStructData = 打包记录 LowerPointer:整数; HigherPointer:整数; 行:整数; 字:整数; 字符:整数; IsPublic : 布尔值; IsPrivate : 布尔值; IsStatic:布尔值; IsFinal : 布尔值; HasNested:布尔值; 结尾; TCoreStruc = 类(TObject) 私人的 FCoreData:TCoreStructData; FNestedStruc : TCoreStruc; 过程 SetNestedStruc(AValue: TCoreStruc); 上市 核心类型:字符串; 名称:字符串; 构造函数 Create();超载; 过程 WriteToStream(Stream: TStream); 程序 ReadFromStream(Stream: TStream); //构造函数 Create(Name, CoreType : ansistring; NestedStruc : TCoreStruc; IsPublic, IsPrivate, IsStatic, IsFinal : boolean);超载; //过程 UpdateValues(NestedStruc : TCoreStruc; IsPublic, IsPrivate, IsStatic, IsFinal : boolean); //过程 SetPosition(Line, Word, Char : integer); 属性LowerPointer:整数读取FCoreData.LowerPointer写入FCoreData.LowerPointer; 属性 HigherPointer:整数读取 FCoreData.HigherPointer 写入 FCoreData.HigherPointer; 属性 Line:整数 读取 FCoreData.Line 写入 FCoreData.Line; 属性 Word:整数 读取 FCoreData.Word 写入 FCoreData.Word; 属性 Char:整数 读取 FCoreData.Char 写入 FCoreData.Char; 属性 NestedStruc: TCoreStruc 读取 FNestedStruc 写入 SetNestedStruc; 结尾; 程序 Main(); 执行 函数 ReadUTF8StringFromStream(const Stream: TStream): String; 变量 n:整数; Buffer8:Utf8String; 开始 结果 := ''; Stream.ReadBuffer(n, SizeOf(n)); 如果 n = 0 那么 出口; SetLength(Buffer8, n); Stream.ReadBuffer(指针(Buffer8)^, n); 结果:=字符串(缓冲区8); 结尾; 过程 WriteUtf8StringToStream(const Data: String; Stream: TStream); 变量 Buffer8:Utf8String; n:整数; 开始 // 在写字符串的时候,我们需要确保字符串的长度 // 字符串被写出到流中。这是第一个,所以 // 读者知道缓冲区有多长。 // // 你可以根据不同的格式写入文件 // 需要。我喜欢在写出文件时使用 UTF8,但确实如此 // 将其转回本机时需要额外的缓冲区副本 // Delphi Unicode 字符串。 Buffer8 := Utf8String(Data); n := 长度(Buffer8); Stream.WriteBuffer(n, SizeOf(n)); Stream.WriteBuffer(指针(Buffer8)^, n); 结尾; 程序 Main(); 变量 结构体:TCoreStruc 数组; 数组大小:整数; 数据流:TMemoryStream; ArraySize_A:整数; Structs_A:TCoreStruc 数组; i:整数; 开始 // 创建并写入一些数据 设置长度(结构,3); 结构[0] := TCoreStruc.Create(); 结构[0].HigherPointer := 1; Structs[0].Name := '测试'; 结构[0].NestedStruc := TCoreStruc.Create(); 结构[0].NestedStruc.HigherPointer := 100; 结构[1] := TCoreStruc.Create(); 结构[1].HigherPointer := 2; 结构[2] := TCoreStruc.Create(); 结构[2].HigherPointer := 3; 数据流 := TMemoryStream.Create(); // 我们需要从我们写出的计数开始 // 读者知道循环多少次。 ArraySize := 长度(结构); DataStream.WriteBuffer(ArraySize, SizeOf(integer)); 对于 i := 0 到 ArraySize - 1 做 开始 结构[i].WriteToStream(DataStream); 结尾; // 将数据读入一组新的对象 数据流.位置:= 0; DataStream.ReadBuffer(ArraySize_A, SizeOf(integer)); SetLength(Structs_A, ArraySize_A); 对于 i := 0 到 ArraySize_A - 1 做 开始 Structs_A[i] := TCoreStruc.Create(); Structs_A[i].ReadFromStream(DataStream); 结尾; 结尾; { TCoreStruc } 构造函数 TCoreStruc.Create; 开始 Self.LowerPointer := 0; Self.HigherPointer := 0; Self.Line := 0; Self.Word := 0; Self.Char := 0; Self.NestedStruc := nil; 结尾; 过程 TCoreStruc.WriteToStream(Stream: TStream); 开始 Stream.WriteBuffer(FCoreData, SizeOf(TCoreStructData)); WriteUtf8StringToStream(名称,流); WriteUtf8StringToStream(CoreType, Stream); 如果 FCoreData.HasNested = true 那么 开始 FNestedStruc.WriteToStream(流) 结尾; 结尾; 过程 TCoreStruc.ReadFromStream(Stream: TStream); 开始 Stream.ReadBuffer(FCoreData, SizeOf(TCoreStructData)); 名称 := ReadUtf8StringFromStream(Stream); 名称 := ReadUtf8StringFromStream(Stream); 如果 FCoreData.HasNested = true 那么 开始 FNestedStruc := TCoreStruc.Create(); FNestedStruc.ReadFromStream(Stream); 结尾; 结尾; 过程 TCoreStruc.SetNestedStruc(AValue: TCoreStruc); 开始 FNestedStruc := AValue; 如果 AValue = nil 则 FCoreData.HasNested := 假 别的 FCoreData.HasNested := true; 结尾; 结尾。

【讨论】:

  • 您真的不想逐个字段地手写所有这些。使用序列化库,并将其转换为 JSON 等标准格式。
  • 在某些情况下,手动操作会很有用。就我而言,我将大量图像数据存储在平均 50-100 MB 的文件中。我没有那么多字段类型——只有很多大字段。对于某些序列化库,控制加载保存过程并包括进度条之类的内容可能很困难。可能不是原始问题的问题。
猜你喜欢
  • 2014-09-25
  • 2017-10-18
  • 2022-01-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多