代码之家  ›  专栏  ›  技术社区  ›  Ian Boyd

Delphi:使用reset/readln读取文本文件的替代方法

  •  12
  • Ian Boyd  · 技术社区  · 14 年前

    我想一行一行地处理文本文件。以前我把文件加载到 StringList :

    slFile := TStringList.Create();
    slFile.LoadFromFile(filename);
    
    for i := 0 to slFile.Count-1 do
    begin
       oneLine := slFile.Strings[i];
       //process the line
    end;
    

    问题是,一旦文件达到几百兆字节,我就必须分配一个 巨大的 大量的记忆;当我真的只需要足够的记忆来一次容纳一行时。(另外,在步骤1中,当系统锁定加载文件时,不能真正指示进度)。

    我尝试使用Delphi提供的本机和推荐的文件I/O例程:

    var
       f: TextFile;
    begin
       Reset(f, filename);
       while ReadLn(f, oneLine) do
       begin
           //process the line
       end;
    

    问题 Assign 如果没有锁定(即 fmShareDenyNone )前者 stringlist 示例也不支持无锁,除非将其更改为 LoadFromStream :

    slFile := TStringList.Create;
    stream := TFileStream.Create(filename, fmOpenRead or fmShareDenyNone);
       slFile.LoadFromStream(stream);
    stream.Free;
    
    for i := 0 to slFile.Count-1 do
    begin
       oneLine := slFile.Strings[i];
       //process the line
    end;
    

    所以现在,即使我没有获得被持有的锁,我还是要将整个文件加载到内存中。

    还有别的办法吗 赋值 / ReadLn ,在那里我可以一行一行地读取文件,而不需要使用共享锁?

    我不想直接进入Win32 CreateFile / ReadFile 必须处理分配缓冲区和检测 CR , LF , CRLF s。

    我考虑过内存映射文件,但是如果整个文件不适合(映射)到虚拟内存中,并且必须一次映射文件的视图(片段),就有困难了。开始变丑了。

    我只是想要 Reset 具有 呋喃丹炔酮 !

    7 回复  |  直到 7 年前
        1
  •  15
  •   Rob Kennedy    14 年前

    对于最新的Delphi版本,您可以使用 TStreamReader . 用文件流构造它,然后调用 its ReadLine method (继承) TTextReader )

    所有Delphi版本的一个选项是使用 Peter Below's StreamIO unit ,这给了你 AssignStream . 它的工作原理就像 AssignFile ,但对于流而不是文件名。一旦使用该函数将流与 TextFile 变量,可以调用 ReadLn 和它上面的其他I/O函数一样。

        2
  •  3
  •   gabr    14 年前

    如果需要在旧Delphis中支持ANSI和Unicode,可以使用 GpTextFile GpTextStream .

        3
  •  3
  •   Linas    14 年前

    您可以使用此示例代码:

    TTextStream = class(TObject)
          private
            FHost: TStream;
            FOffset,FSize: Integer;
            FBuffer: array[0..1023] of Char;
            FEOF: Boolean;
            function FillBuffer: Boolean;
          protected
            property Host: TStream read FHost;
          public
            constructor Create(AHost: TStream);
            destructor Destroy; override;
            function ReadLn: string; overload;
            function ReadLn(out Data: string): Boolean; overload;
            property EOF: Boolean read FEOF;
            property HostStream: TStream read FHost;
            property Offset: Integer read FOffset write FOffset;
          end;
    
        { TTextStream }
    
        constructor TTextStream.Create(AHost: TStream);
        begin
          FHost := AHost;
          FillBuffer;
        end;
    
        destructor TTextStream.Destroy;
        begin
          FHost.Free;
          inherited Destroy;
        end;
    
        function TTextStream.FillBuffer: Boolean;
        begin
          FOffset := 0;
          FSize := FHost.Read(FBuffer,SizeOf(FBuffer));
          Result := FSize > 0;
          FEOF := Result;
        end;
    
        function TTextStream.ReadLn(out Data: string): Boolean;
        var
          Len, Start: Integer;
          EOLChar: Char;
        begin
          Data:='';
          Result:=False;
          repeat
            if FOffset>=FSize then
              if not FillBuffer then
                Exit; // no more data to read from stream -> exit
            Result:=True;
            Start:=FOffset;
            while (FOffset<FSize) and (not (FBuffer[FOffset] in [#13,#10])) do
              Inc(FOffset);
            Len:=FOffset-Start;
            if Len>0 then begin
              SetLength(Data,Length(Data)+Len);
              Move(FBuffer[Start],Data[Succ(Length(Data)-Len)],Len);
            end else
              Data:='';
          until FOffset<>FSize; // EOL char found
          EOLChar:=FBuffer[FOffset];
          Inc(FOffset);
          if (FOffset=FSize) then
            if not FillBuffer then
              Exit;
          if FBuffer[FOffset] in ([#13,#10]-[EOLChar]) then begin
            Inc(FOffset);
            if (FOffset=FSize) then
              FillBuffer;
          end;
        end;
    
        function TTextStream.ReadLn: string;
        begin
          ReadLn(Result);
        end;
    

    用途:

    procedure ReadFileByLine(Filename: string);
    var
      sLine: string;
      tsFile: TTextStream;
    begin
      tsFile := TTextStream.Create(TFileStream.Create(Filename, fmOpenRead or    fmShareDenyWrite));
      try
        while tsFile.ReadLn(sLine) do
        begin
          //sLine is your line
        end;
      finally
        tsFile.Free;
      end;
    end;
    
        4
  •  2
  •   Uwe Raabe    14 年前

    似乎是 文件格式 变量对textfiles无效,但我的测试表明多次读取该文件没有问题。你没有在你的问题中提到它,但是如果你不打算在读文本文件的时候写它,你应该是好的。

        5
  •  2
  •   lkessler    7 年前

    我要做的是使用一个tfilestream,但是我将输入缓冲到相当大的块中(例如,每个块都有几个兆字节),然后一次读取和处理一个块。这样我就不必一次加载整个文件。

    这样做的速度非常快,即使对于大型文件也是如此。

    我有进度指示器。当我加载每个块时,我会增加额外加载的文件的分数。

    一次只读取一行,而不进行缓冲,对于大型文件来说,速度太慢了。

        6
  •  0
  •   Deltics    14 年前

    为什么不直接从tfilestream本身一次读取一行文件?

    即(伪代码):

      readline: 
        while NOT EOF and (readchar <> EOL) do
          appendchar to result
    
    
      while NOT EOF do
      begin
        s := readline
        process s
      end;
    

    您可能会发现这样一个问题:IIRC tfilestream没有缓冲,因此大型文件的性能将是次优的。但是,对于非缓冲流的问题有许多解决方案, including this one ,您可能希望研究这种方法是否解决了您的初始问题。

        7
  •  0
  •   HpTerm vilpe89    13 年前

    几年前我也遇到过同样的问题,尤其是锁定文件的问题。我所做的就是使用shellapi中的低级readfile。我知道这个问题从我的答案(2年)起就已经过时了,但也许我的贡献可以帮助未来的某个人。

    const
      BUFF_SIZE = $8000;
    var
      dwread:LongWord;
      hFile: THandle;
      datafile : array [0..BUFF_SIZE-1] of char;
    
    hFile := createfile(PChar(filename)), GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, 0);
    SetFilePointer(hFile, 0, nil, FILE_BEGIN);
    myEOF := false;
    try
      Readfile(hFile, datafile, BUFF_SIZE, dwread, nil);   
      while (dwread > 0) and (not myEOF) do
      begin
        if dwread = BUFF_SIZE then
        begin
          apos := LastDelimiter(#10#13, datafile);
          if apos = BUFF_SIZE then inc(apos);
          SetFilePointer(hFile, aPos-BUFF_SIZE, nil, FILE_CURRENT);
        end
        else myEOF := true;
        Readfile(hFile, datafile, BUFF_SIZE, dwread, nil);
      end;
    finally
       closehandle(hFile);
    end;
    

    对我来说,速度的提高似乎是显著的。