代码之家  ›  专栏  ›  技术社区  ›  Kenneth Cochran

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

  •  1
  • Kenneth Cochran  · 技术社区  · 16 年前

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

    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 .

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

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

    • 这是处理这类文件的好方法吗?
    • 如果是的话,你会如何执行 程序看起来怎么样(我处理过分隔文件,但这是我第一次遇到固定长度字段)
    • 如果没有,你会采取什么方法?

    更新

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

    我和你一起去的 Rob's Copy 结合适当的 StrTo*

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

    常量比调用中的“幻数”更容易阅读 复制 .

    如有其他建议,将不胜感激。

    4 回复  |  直到 9 年前
        1
  •  2
  •   Cosmin Prund    16 年前

    我要改变一件事:用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.');
    
        2
  •  1
  •   Rob Kennedy    16 年前

    我看还行。要提取字段,可以使用 Copy 标准功能。将输入字符串、字段第一个字符的索引和字符数赋给它,它将以新字符串的形式返回该部分,然后可以将该部分赋给另一个字符串变量或传递给另一个函数进行进一步的转换,例如 StrToInt .

        3
  •  1
  •   user160694 user160694    16 年前

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

    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;
    

        4
  •  0
  •   Robert Love    16 年前

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

    有一件事你没有指定是你的领域将如何工作。因为它们是固定长度的,所以我会考虑将它们设为属性,这样在属性的Set方法中就可以验证长度。