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

在Delphi中读取具有固定长度字段和记录的文本文件,delphi,file-io,fixed-length-record,Delphi,File Io,Fixed Length Record,我需要从字段长度和记录长度固定的文本文件中读取数据。字段可以是零填充的,也可以是空格填充的,总是以相同的顺序出现,并且每条记录都由CRLF终止。文件可以具有由记录中的第一个字符确定的三种可能的记录类型之一 到目前为止,我已经为所有记录类型创建了一个基类,并为每个记录类型创建了一个子类 type TRecordBase = class abstract public // Various common fields... function ToString: string;

我需要从字段长度和记录长度固定的文本文件中读取数据。字段可以是零填充的,也可以是空格填充的,总是以相同的顺序出现,并且每条记录都由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

其思想是记录类可以用于读取和写入记录的字符串表示形式。
Read
过程需要分解字符串并将其分配给公共字段

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

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

我只是想填上一些遗漏的细节。这些记录类本质上是DTO(数据传输对象)。这些字段被声明为公共字段,并且唯一的方法用于与字符串的转换。字段上唯一的数据验证是编译器的类型检查。使用
TStringBuilder.AppendFormat
将字段按所需顺序转换为字符串。这确保字段填充和/或截断到适当的长度

我建议使用
Copy
结合适当的
StrTo*
从字符串中获取数据。我还将字段位置和长度定义为类常量,即

const Field1Pos = 1;
const Field1Length = 1;
const Field2Pos = 2;
const Field2Length = 5;
在调用
Copy
时,常量比“幻数”更容易读取


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

在我看来没问题。要提取字段,可以使用标准函数。给它输入字符串、字段第一个字符的索引和字符数,它将以新字符串的形式返回该部分,然后您可以将其分配给另一个字符串变量或传递给另一个函数进行进一步的转换,例如
stroint

在我看来是可以的。要提取字段,可以使用标准函数。给它输入字符串、字段第一个字符的索引和字符数,它将以新字符串的形式返回该部分,然后您可以将其分配给另一个字符串变量或传递给另一个函数进行进一步转换,例如
strotint

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


您没有指定的一件事是字段的工作方式。因为它们是固定长度的,所以我会考虑在属性的集合方法中使它们成为属性,这样就可以验证长度。

< P>我认为您的方法是一个非常优雅的解决方案。
您没有指定的一件事是字段的工作方式。由于它们是固定长度的,所以我想在属性的SET方法中使它们成为属性,这样就可以验证长度。

< P>我会改变一件事:用一个读构造函数替换读过程,类似这样的:

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

TRecordType1 = class(TRecordBase)
public
  constructor CreateFromText(Text:string);override;
end;
// 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.');
根据您对记录所做的操作,这将节省一些输入并使代码更易于阅读:

var s:string; // string from stream or string-list
if s[1] = 'X'then DoSomethingWith(TRecordType1.Create(s));
如果记录类型的数量增加,拥有虚拟构造函数也很方便。您可以这样做:

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

TRecordType1 = class(TRecordBase)
public
  constructor CreateFromText(Text:string);override;
end;
// 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.');
//定义一个类类型
类型TRecordBaseClass=TRecordBase的类别;
//使用Delphi2010?使用字典注册(FirstChar,TRecordBaseClass)paris
var RecordClassDictionary=t字典;
//像这样初始化字典:
RecordClassDictionary.Add('1',TRecordType1);
RecordClassDictionary.Add('2',TRecordType2);
RecordClassDictionary.Add('3',TRecordType3);
//然后像这样使用它:
var-RecordBaseClass:TRecordBaseClass;
对于文本中的行toparse do
如果是RecordClassDictionary.TryGetValue(第[1]行,RecordBaseClass),则
//读记录,用记录做点什么
DoSomethingWithRecord(RecordBaseClass.CreateFromText(行))
其他的
引发异常。创建('未知记录类型');

我想改变一件事:用读取构造函数替换读取过程,如下所示:

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

TRecordType1 = class(TRecordBase)
public
  constructor CreateFromText(Text:string);override;
end;
// 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.');
根据您对记录所做的操作,这将节省一些输入并使代码更易于阅读:

var s:string; // string from stream or string-list
if s[1] = 'X'then DoSomethingWith(TRecordType1.Create(s));
如果记录类型的数量增加,拥有虚拟构造函数也很方便。您可以这样做:

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

TRecordType1 = class(TRecordBase)
public
  constructor CreateFromText(Text:string);override;
end;
// 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.');
//定义一个类类型
类型TRecordBaseClass=TRecordBase的类别;
//使用Delphi2010?使用字典注册(FirstChar,TRecordBaseClass)paris
var RecordClassDictionary=t字典;
//像这样初始化字典:
RecordClassDictionary.Add('1',TRecordType1);
RecordClassDictionary.Add('2',TRecordType2);
RecordClassDictionary.Add('3',TRecordType3);
//然后像这样使用它:
var-RecordBaseClass:TRecordBaseClass;
对于文本中的行toparse do
如果是RecordClassDictionary.TryGetValue(第[1]行,RecordBaseClass),则
//读记录,用记录做点什么
DoSomethingWithRecord(RecordBaseClass.CreateFromText(行))
其他的
引发异常。创建('未知记录类型');

如果字段长度和记录长度是固定的,我会将几乎被遗忘的记录与一个可变部分一起使用:

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版本,那么您也可以使用它们来访问字段。

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

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版本,那么您也可以使用它们来访问字段。

Ah,对抽象工厂模式进行了一点修改。我喜欢。我怀疑这个号码