在过去,我看过这项工作,但我从未真正理解应如何完成。
假设我们有已知数据类型的文件,但未知长度,就像动态数组TSomething
一样,其中
type
TSomething = class
Name: String;
Var1: Integer;
Var2: boolean;
end;
问题是,此对象类型可能会在将来扩展,添加更多变量(例如Var3: String
)。
然后,使用旧版本保存的文件将不包含最新变量
文件读取过程应以某种方式识别块中的数据,使用如下算法:
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;
我已经看到这项工作与BlockRead
和BlockWrite
,我假设每个对象应该在写入文件之前写出它的大小,但我会欣赏一个例子(不一定是代码),要知道我正朝着正确的方向思考。
相关读物我发现:
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?
答案 0 :(得分:7)
为了使这项工作,您需要将元素大小写入文件。然后,当您阅读该文件时,您会读取该元素长度,该元素长度允许您读取每个元素,即使您的程序不知道如何理解所有元素。
在将记录与磁盘记录匹配方面,如果您的记录只包含简单类型,那么这很容易。在这种情况下,您可以从文件Min(ElementLength, YourRecordSize)
字节读取到您的记录中。
但它看起来并不像你真的那样。您的记录实际上是一个类,因此不适合内存复制。更重要的是,它的第一个成员是一个绝对不是简单类型的字符串。
在当天(比如20世纪70年代),你所描述的技术是文件的阅读方式。但是现在节目已经开始了。将结构化数据保存到文件通常意味着使用更灵活和适应性更强的序列化格式。您应该考虑使用JSON,XML,YAML或类似的任务。
答案 1 :(得分:5)
我说你需要一种版本化文件的方法。这样你就知道文件中包含哪个版本的记录。将其写在文件的开头,然后在读取时,首先读入版本标识符,然后使用相应的结构来读取其余部分。
答案 2 :(得分:1)
如果我理解正确,那么主要问题是TSomething
是否会发生变化。最重要的是你需要在你的文件中添加版本信息,这是你真的无法避免的。
至于使用Sqlite的实际存储最有可能解决您的所有问题,但根据您的情况,这可能是一种过度杀伤。
除了非常规的情况,我不会真的担心过多地扩展课程。如果你在文件的开头添加版本号,你可以在课程改变后轻松转换文件。您需要做的就是实施您的解决方案,以便添加转换就像合理一样简单。
为了读/写文件,我更喜欢使用streams / XML / JSON(取决于情况)而不是blockread / blockwrite,因为您不必实现hack来存储版本号。
理论上你也可以为每条记录留出未使用的空间,这样我就可以避免重新创建整个文件,如果类更改到一个点(直到你有足够的未使用空间)。如果TSomething
经常更改且文件很大,这可能会有所帮助,但很可能我不会那样做。
答案 3 :(得分:1)
我就是这样做的:在标题中包含一个简单的版本号。这可以是任何字符串,整数或其他。
读取和写入文件非常简单(我正在使用伪代码):
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>
,您甚至可以避免修改工厂。