12

テキストファイルを1行ずつ処理したい。昔、私はファイルを次の場所にロードしました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行を保持するのに十分なメモリしか必要ない場合。(さらに、ステップ 1 でファイルのロード中にシステムがロックされている場合、進行状況を実際に示すことはできません)。

Delphi が提供するネイティブで推奨されるファイル I/O ルーチンを使用してみました。

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

問題Assignは、ロックせずにファイルを読み取るオプションがないことです (つまりfmShareDenyNone)。前のstringlist例は、次のように変更しない限り、no-lock もサポートしません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ますか 、共有ロックを取得せずにファイルを1行ずつ読み取ることができますか?

Win32 CreateFile/に直接入りたくないので、バッファの割り当てと, ,のReadFile検出に対処する必要があります。CRLFCRLF

メモリ マップ ファイルについて考えましたが、ファイル全体が仮想メモリに収まらない (マップされない) 場合や、ファイルのビュー (断片) を一度にマップする必要がある場合に問題があります。醜くなり始めます。

私はただReset欲しいfmShareDenyNone

4

7 に答える 7

16

最近の Delphi バージョンでは、TStreamReader. ファイル ストリームを使用してそれを構築し、そのReadLineメソッドを呼び出します(から継承TTextReader)。

すべての Delphi バージョンのオプションは、Peter Below のStreamIO unitを使用することですAssignStream。と同じように機能AssignFileしますが、ファイル名ではなくストリーム用です。その関数を使用してストリームをTextFile変数に関連付けるReadLnと、他のファイルと同じように、他の I/O 関数を呼び出すことができます。

于 2010-05-12T05:21:47.530 に答える
4

次のサンプル コードを使用できます。

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;
于 2010-06-02T12:24:42.040 に答える
3

FileMode変数はテキストファイルには有効ではないようですが、私のテストでは、ファイルからの複数の読み取りは問題ないことが示されました。あなたの質問ではそれについて言及していませんでしたが、テキストファイルが読み取られている間にテキストファイルに書き込むつもりがない場合は、うまくいくはずです。

于 2010-05-12T06:22:58.507 に答える
3

古い Delphis で ansi と Unicode のサポートが必要な場合は、私のGpTextFileまたはGpTextStreamを使用できます。

于 2010-05-12T06:45:45.790 に答える
2

私は TFileStream を使用していますが、入力をかなり大きなブロック (たとえば、それぞれ数メガバイト) にバッファリングし、一度に 1 つのブロックを読み取って処理します。そうすれば、ファイル全体を一度にロードする必要はありません。

大きなファイルであっても、そのように非常に迅速に機能します。

進行状況インジケーターがあります。各ブロックをロードするときに、追加でロードされたファイルの分数をインクリメントします。

バッファリングを行わずに一度に 1 行ずつ読み取るのは、大きなファイルには遅すぎます。

于 2010-05-12T05:14:46.540 に答える
1

数年前、特にファイルをロックするという同じ問題がありました。私がしたことは、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;

私にとって、速度の改善は重要なように見えました。

于 2012-01-18T15:23:30.147 に答える
0

ファイルの行を TFileStream 自体から一度に 1 つずつ直接読み取らないのはなぜでしょうか。

すなわち(擬似コードで):

  readline: 
    while NOT EOF and (readchar <> EOL) do
      appendchar to result


  while NOT EOF do
  begin
    s := readline
    process s
  end;

これで見つかる問題の 1 つは、iirc TFileStream がバッファリングされていないため、大きなファイルのパフォーマンスが最適ではないということです。ただし、バッファリングされていないストリームの問題には、このアプローチを含め、いくつかの解決策があります。このアプローチで最初の問題が解決するかどうかを調査することをお勧めします。

于 2010-05-12T04:24:29.517 に答える