在Delphi中读取具有固定长度字段和记录的文本文件

时间:2010-07-08 14:35:06

标签: delphi file-io fixed-length-record

我需要从文本文件中读取数据,其中字段长度和记录长度是固定的。字段为零填充或空格填充,始终以相同的顺序出现,每个记录由CRLF终止。该文件可以具有由记录中的第一个字符确定的三种可能记录类型之一。

到目前为止,我已经为所有记录类型创建了一个基类,并为每个记录类型创建了一个子类。

type
  TRecordBase = class abstract
  public
    // Various common fields...
    function ToString: string; virtual; abstract;
    procedure Read(AString: string); virtual; abstract;
  end;

  TRecordType1 = class(TRecordBase)
  public
    //RecordType1 fields...
    function ToString: string; override;
    procedure Read(AString: string); override;
  end;

  TRecordType2 = class(TRecordBase)
  public
    //RecordType2 fields...
    function ToString: string; override;
    procedure Read(AString: string); override;
  end;

  TRecordType3 = class(TRecordBase)
  public
    //RecordType3 fields...
    function ToString: string; override;
    procedure Read(AString: string); override;
  end;

然后我只是将文件的每一行作为字符串读取,从第一个字符确定其类型,创建适当的类实例并调用Read

这个想法是Record类可以用于读取和写入记录的字符串表示。 Read过程需要拆分字符串并将其分配给公共字段。

我有两个(或三个)问题:

  • 这是处理此类文件的好方法吗?
  • 如果是这样,您对Read程序的实现会是什么样的? (我已经处理了分隔文件,但这是我第一次遇到固定长度字段)
  • 如果没有,你会采取什么方法?

更新

我以为我会填写一些遗漏的细节。这些记录类本质上是DTO(数据传输对象)。这些字段被声明为public,唯一的方法是转换为字符串/从字符串转换。字段上唯一的数据验证是编译器的类型检查。使用TStringBuilder.AppendFormat按要求的顺序将字段转换为字符串。这确保了字段被填充和/或截断到适当的长度。

我提出了Rob's建议,使用Copy结合相应的StrTo*来获取字符串中的数据。我还将场位置和长度定义为类常数,即

const Field1Pos = 1;
const Field1Length = 1;
const Field2Pos = 2;
const Field2Length = 5;

Copy的调用中,这些内容比“魔术数字”更容易阅读。

任何其他建议将不胜感激。

4 个答案:

答案 0 :(得分:2)

我改变了一件事:用Read构造函数替换read过程,如下所示:

TRecordBase = class
public
  constructor CreateFromText(Text:string);virtual;abstract;
end;

TRecordType1 = class(TRecordBase)
public
  constructor CreateFromText(Text:string);override;
end;

根据您对记录的处理方式,这将节省一些打字并使代码更易于阅读:

var s:string; // string from stream or string-list
if s[1] = 'X'then DoSomethingWith(TRecordType1.Create(s));

如果记录类型的数量增加,拥有虚拟构造函数也很方便。你可以这样做:

// Define an class type
type TRecordBaseClass = class of TRecordBase;

// Using Delphi 2010? Use a dictionary to register (FirstChar, TRecordBaseClass) paris
var RecordClassDictionary = TDictionary<char, TRecordBaseClass>;

// Init the dictionary like this:
RecordClassDictionary.Add('1', TRecordType1);
RecordClassDictionary.Add('2', TRecordType2);
RecordClassDictionary.Add('3', TRecordType3);

// And use it like this:
var RecordBaseClass: TRecordBaseClass;
for line in TextToParse do
  if RecordClassDictionary.TryGetValue(line[1], RecordBaseClass) then
     // Read the record, do something with the record
     DoSomethingWithTheRecord(RecordBaseClass.CreateFromText(line))
  else
     raise Exception.Create('Unkown record type.');

答案 1 :(得分:1)

对我来说还不错。要提取字段,可以使用Copy标准函数。给它输入字符串,字段的第一个字符的索引和字符数,它将该部分作为新字符串返回,然后您可以将其分配给另一个字符串变量或传递给另一个函数以进行进一步转换,例如StrToInt

答案 2 :(得分:1)

如果字段长度和记录长度是固定的,我会使用几乎被遗忘的记录和变体部分:

TRecord1 = packed record
  A: array[0..10] of char;
end;

TRecord2 = packed record
  B: array[0..20] of Byte;
  C: array[0..5] of Byte;
end;

TRecord3 = packed record
  D: array[0..10] of Byte;
  E: array[0..15] of Byte;
  F: array[0..1] of Byte;
end;


TMyRecord = packed record
  case RecordType: Char of
    '1': (Rec1: TRecord1);
    '2': (Rec2: TRecord2);
    '3': (Rec3: TRecord3);
end;

S := ReadLn;

with TMyRecord(S[1]) do
begin
  ...
end;

如果您使用的是支持记录方法的Delphi版本,您也可以使用它们来访问字段。

答案 3 :(得分:0)

我认为您的方法是一种非常优雅的解决方案。

您未指定的一件事是您的字段将如何工作。由于它们是固定长度,我会考虑将它们作为属性,因此在属性的Set Method中可以验证长度。