【问题标题】:Effectively using Delphi to read unknown-sized blocks from a file有效地使用 Delphi 从文件中读取未知大小的块
【发布时间】:2013-10-03 06:25:08
【问题描述】:

在过去,我看过这项工作,但我从来没有真正理解应该如何完成
假设我们有一个已知数据类型的文件,但未知长度,比如TSomething的动态数组,其中

type
  TSomething = class
    Name: String;
    Var1: Integer;
    Var2: boolean;
  end;

但问题在于,这种对象类型将来可能会扩展,添加更多变量(例如Var3: String)。
然后,使用旧版本保存的文件将不包含最新的变量
文件读取程序应该以某种方式识别 blocks 中的数据,其算法如下:

procedure Read(Path: String)
begin
  // Read Array Size
  //   Read TSomething --> where does this record end? May not contain Var3!
  //   --> how to know that the next data block I read is not a new object?
end;

我已经看到与BlockReadBlockWrite 一起工作,并且我假设每个对象可能应该在将自身写入文件之前写入其大小,但我希望有一个示例(不一定是代码),要知道我我正在朝着正确的方向思考。

我找到的相关读物:
SO - Delphi 2010: How to save a whole record to a file?
Delphi Basics - BlockRead
SO - Reading/writing dynamic arrays of objects to a file - Delphi
SO - How Can I Save a Dynamic Array to a FileStream in Delphi?

【问题讨论】:

    标签: delphi


    【解决方案1】:

    为了完成这项工作,您需要将元素大小写入文件。然后,当您读取文件时,您会读取允许您读取每个完整元素的元素长度,即使您的程序不知道如何理解所有元素。

    如果您的记录仅包含简单类型,那么将您的记录与磁盘上的记录匹配就很容易了。在这种情况下,您可以将文件 Min(ElementLength, YourRecordSize) 字节读取到您的记录中。

    但看起来您实际上并没有这种情况。您的记录实际上是一个类,因此不适合内存复制。更重要的是,它的第一个成员是一个字符串,绝对不是一个简单的类型。

    在过去(比如 1970 年代),您描述的技术是文件的读取方式。但是这些天,编程已经开始了。将结构化数据保存到文件通常意味着使用更灵活和适应性更强的序列化格式。您应该考虑使用 JSON、XML、YAML 或类似方法来完成此类任务。

    【讨论】:

    • 谢谢大卫。这是客观的,但我认为您解决了阅读未知大小和序列化问题。谢谢。
    【解决方案2】:

    我会说您需要一种对文件进行版本控制的方法。这样您就知道文件中包含什么版本的记录。写在文件的开头,读的时候先读入版本标识,然后用对应的结构来读剩下的。

    【讨论】:

    • 这是我使用的方法,效果很好。文件前面的标题,包含标识文件类型的 ID、标识文件布局和使用的记录类型的版本号,以及控制记录数据解释而不影响如何读取数据(字符编码、特征等)。
    • @David,所以你认为如果你完全改变所用结构的整体含义,旧程序需要知道文件中有哪些数据?我不。我只想告诉用户他运气不好,他应该得到我的程序的更新版本。
    • 就个人而言,我更喜欢以更易于访问的格式写入数据,我更喜欢 xml,这样旧版本的程序可以读取新文件。
    • @TLama 不,我不这么认为,也从未这么说过。旧文件只需要知道如何跳过它不理解的信息。一个版本是不够的。需要更多的结构。我的程序的二进制文件可以被古代版本读取。
    • 标题中的版本信息就足够了,因为 ta 的解释可以很容易地在代码中完成。它只需要一个组织良好的读者。我会推荐一个简单的工厂,它可以为给定的版本号检索足够的阅读器。显然并且通常,版本 X 的阅读器源自版本 X-1 的阅读器。所以每个阅读器中的编码很少。
    【解决方案3】:

    如果我对您的理解正确,您的主要问题是TSomething 是否发生变化。最重要的是您需要将版本信息添加到文件中,这是您无法避免的。

    至于使用Sqlite 的实际存储很可能会解决您的所有问题,但根据您的情况,这可能是矫枉过正。

    除非特殊情况,我真的不会担心过多地扩展类。如果您在文件开头添加添加版本号,您可以在类更改后轻松转换文件。您需要做的就是实施您的解决方案,以便添加转换尽可能简单合理。

    为了读/写文件,我更喜欢流/XML/JSON(取决于情况)而不是 blockread/blockwrite,因为您不必实施 hack 来存储版本号。

    理论上,您还可以为每条记录保留未使用的空间,因此如果类更改到某个点(直到您有足够的未使用空间),您可以避免重新创建整个文件。如果TSomething 经常更改并且文件很大,这可能会有所帮助,但我很可能不会走那条路。

    【讨论】:

    • 感谢您的建议。对于这样的小任务,我不会使用 SQL,但我肯定会考虑将其转换为 XML 或 JSON,可能。
    【解决方案4】:

    我会这样做:在标题中包含一个简单的版本号。这可以是任何字符串、整数或其他任何内容。

    文件的读写非常简单(我用的是伪代码):

    Procedure Read (MyFile : TFile);
    Var
      reader : IMyFileReader;
    
    begin
      versionInfo = MyFile.ReadVersionInfo();
      reader = ReaderFactory.CreateFromVersion(versionInfo);
      reader.Read(MyFile);
    end;
    
    
    Type
      ReaderFactory = Class
      public 
        class function CreateFromVersion(VersionInfo : TVersionInfo) : IMyFileReader;
      end;
    
    function ReaderFactory.CreateFromVersion(VersionInfo : TVersionInfo) : IMyFileReader;
    begin
      if VersionInfo = '0.9-Alpha' then
        result := TVersion_0_9_Alpha_Reader.Create()
      else if VersionInfo = '1.0' then
        result := TVersion1_0_Reader.Create()
      else ....
    end;
    

    这可以很容易地永久维护和扩展。您将永远不必触摸读取例程,而只需添加一个新的读取器并增强工厂。通过简单的注册方法和TDictionary<TVersionInfo,TMyFileReaderClass>,您甚至可以避免修改工厂。

    【讨论】:

    • 这与 Pieter B 的回答相同,并且存在旧程序无法读取新文件的问题。
    • 如果无法区分未版本化文件和版本化文件,那么任何旧程序都无法读取版本化文件。您要么开始将版本信息添加到文件中,要么从一开始就使用结构化文件格式,否则就会陷入困境。
    • 知道文件是不同的格式并不允许您阅读它。使用结构化格式(可能是 JSON、XML,或者可能是自制二进制格式,或者可能是很多其他的东西)确实允许这样做。不管怎样,你的评论和我的一致。
    • 我的解决方案在我看来是解决上述问题的最简单的解决方案,即:使用更新的软件读取旧数据。在实践中,我使用久经考验的方法,而不是试图重新发明轮子。我不喜欢自制格式来存储数据并尽量避免使用它们。 (有时你必须这样做)
    猜你喜欢
    • 2018-08-31
    • 1970-01-01
    • 2011-12-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-24
    • 1970-01-01
    • 2015-07-07
    相关资源
    最近更新 更多