Delphi:使用Reset / ReadLn替代文本文件读取

时间:2010-05-12 02:39:53

标签: delphi text-files readline

我想逐行处理文本文件。在过去,我将文件加载到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;

所以现在即使我没有获得锁定,我又回来将整个文件加载到内存中。

是否有Assign / ReadLn的替代方案,我可以逐行读取文件,而无需进行共享锁定?

我宁愿不直接进入Win32 CreateFile / ReadFile,也不得不处理分配缓冲区并检测CRLFCRLF的。

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

我只想ResetfmShareDenyNone一起使用!

7 个答案:

答案 0 :(得分:15)

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

所有Delphi版本的选项是使用Peter Below's StreamIO unit,它会为您提供AssignStream。它的工作方式与AssignFile类似,但对于流而不是文件名。一旦您使用该函数将流与TextFile变量相关联,您就可以像调用任何其他文件一样调用ReadLn和其他I / O函数。

答案 1 :(得分:3)

如果您需要在较早的Delphis中支持ansi和Unicode,则可以使用我的GpTextFileGpTextStream

答案 2 :(得分:3)

您可以使用以下示例代码:

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;

答案 3 :(得分:2)

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

这种方式非常快,即使是大文件也是如此。

我确实有进度指示器。当我加载每个块时,我将它增加了另外加载的文件的分数。

一次只读一行,无需缓冲,对大文件来说太慢了。

答案 4 :(得分:2)

因为似乎 FileMode 变量对Textfiles无效,但我的测试显示从文件中多次读取没有问题。你没有在你的问题中提到它,但如果你在阅读时不打算写文本文件,你应该很好。

答案 5 :(得分:0)

为什么不直接从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的问题,有很多解决方案,如果这种方法可以解决您的初始问题,您可能希望进行调查。

答案 6 :(得分:0)

几年前我遇到了同样的问题,尤其是锁定文件的问题。我所做的是使用shellapi的低级读取文件。我知道问题已经过去了,因为我的回答(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;

对我而言,速度提升似乎很重要。