2

私は Delphi でテキスト ファイルを扱っていますが、文字列リストをロード/保存する方法を使用したくありません。メモリではなくハードディスクに大量のデータを保持して、そこにデータを読み書きするオープンファイルストリームを維持するつもりです。新しい行をテキスト ファイルに書き込んで読み取るという単純な概念がありますが、それらを変更および削除する場合、適切なリソースが見つかりません。

このファイルの各行には名前と等号が含まれ、残りはデータです。たとえば、SOMEUNIQUENAME=SomeStringValue. スレッド内で一定期間ファイルを開いたままにするつもりです。このスレッドは、データの特定のフィールドを取得、設定、または削除する受信要求を実行します。WriteLnandReadLnをループで使用して、 を評価しEOFます。以下は、データの読み取り方法の例です。

FFile = TextFile;

...

function TFileWrapper.ReadData(const Name: String): String;
var
  S: String; //Temporary line to be parsed
  N: String; //Temporary name of field
begin
  Result:= '';
  Reset(FFile);
  while not EOF(FFile) do begin
    ReadLn(FFile, S);
    N:= UpperCase(Copy(S, 1, Pos('=', S)-1));
    if N = UpperCase(Name) then begin
      Delete(S, 1, Pos('=', S));
      Result:= S;
      Break;
    end;
  end;
end;

...そして、送信者に結果を通知するイベントをトリガーします。リクエストは、これらのリクエストの一種のメッセージ ポンプであるキュー内にあります。スレッドは、通常のアプリケーションの動作と同様に、キュー内の次の要求を繰り返し処理するだけです。

これらのフィールドを書き込んだり削除したりできる手順は用意されていますが、ファイルに対して実際にアクションを実行するために何をしなければならないかわかりません。

procedure TFileWrapper.WriteData(const Name, Value: String);
var
  S: String; //Temporary line to be parsed
  N: String; //Temporary name of field
begin
  Result:= '';
  Reset(FFile);
  while not EOF(FFile) do begin
    ReadLn(FFile, S);
    N:= UpperCase(Copy(S, 1, Pos('=', S)-1));
    if N = UpperCase(Name) then begin
      //How to re-write this line?
      Break;
    end;
  end;
end;

procedure TFileWrapper.DeleteData(const Name: String);
var
  S: String; //Temporary line to be parsed
  N: String; //Temporary name of field
begin
  Result:= '';
  Reset(FFile);
  while not EOF(FFile) do begin
    ReadLn(FFile, S);
    N:= UpperCase(Copy(S, 1, Pos('=', S)-1));
    if N = UpperCase(Name) then begin
      //How to delete this line?
      Break;
    end;
  end;
end;

最後に、これを実現するには、ファイル全体をメモリにロードしないようにする必要があります。

4

3 に答える 3

7

これは興味深い質問だと思うので、小さなコンソール アプリを作成しました。

私は3つの方法を使用しました:

  • TStringList
  • ストリームリーダー/ストリームライター
  • テキストファイル

サイズが 10kb のテキスト ファイルとサイズが 1Mb のテキスト ファイルを使用して、すべての方法を時間を計って 100 回繰り返します。プログラムは次のとおりです。

program Project16;

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes, StrUtils, Diagnostics, IOUtils;

procedure DeleteLine(StrList: TStringList; SearchPattern: String);

var
  Index : Integer;

begin
 for Index := 0 to StrList.Count-1 do
  begin
   if ContainsText(StrList[Index], SearchPattern) then
    begin
     StrList.Delete(Index);
     Break;
    end;
  end;
end;

procedure DeleteLineWithStringList(Filename : string; SearchPattern : String);

var StrList : TStringList;

begin
 StrList := TStringList.Create;
 try
  StrList.LoadFromFile(Filename);
  DeleteLine(StrList, SearchPattern);
  // don't overwrite our input file so we can test
  StrList.SaveToFile(TPath.ChangeExtension(Filename, '.new'));
 finally
  StrList.Free;
 end;
end;

procedure DeleteLineWithStreamReaderAndWriter(Filename : string; SearchPattern : String);

var
  Reader    : TStreamReader;
  Writer    : TStreamWriter;
  Line      : String;
  DoSearch  : Boolean;
  DoWrite   : Boolean;

begin
 Reader := TStreamReader.Create(Filename);
 Writer := TStreamWriter.Create(TPath.ChangeExtension(Filename, '.new'));
 try
  DoSearch := True;
  DoWrite := True;
  while Reader.Peek >= 0 do
   begin
    Line := Reader.ReadLine;
    if DoSearch then
     begin
      DoSearch := not ContainsText(Line, SearchPattern);
      DoWrite := DoSearch;
     end;
    if DoWrite then
     Writer.WriteLine(Line)
    else
     DoWrite := True;
   end;
 finally
  Reader.Free;
  Writer.Free;
 end;
end;

procedure DeleteLineWithTextFile(Filename : string; SearchPattern : String);

var
 InFile    : TextFile;
 OutFile   : TextFile;
 Line      : String;
 DoSearch  : Boolean;
 DoWrite   : Boolean;


begin
 AssignFile(InFile, Filename);
 AssignFile(OutFile, TPath.ChangeExtension(Filename, '.new'));
 Reset(InFile);
 Rewrite(OutFile);
 try
  DoSearch := True;
  DoWrite := True;
  while not EOF(InFile) do
   begin
    Readln(InFile, Line);
    if DoSearch then
     begin
      DoSearch := not ContainsText(Line, SearchPattern);
      DoWrite := DoSearch;
     end;
    if DoWrite then
     Writeln(OutFile, Line)
    else
     DoWrite := True;
   end;
 finally
  CloseFile(InFile);
  CloseFile(OutFile);
 end;
end;

procedure TimeDeleteLineWithStreamReaderAndWriter(Iterations : Integer);

var
  Count : Integer;
  Sw    : TStopWatch;

begin
 Writeln(Format('Delete line with stream reader/writer - file 10kb, %d iterations', [Iterations]));
 Sw := TStopwatch.StartNew;
 for Count := 1 to Iterations do
  DeleteLineWithStreamReaderAndWriter('c:\temp\text10kb.txt', 'thislinewillbedeleted=');
 Sw.Stop;
 Writeln(Format('Elapsed time : %d milliseconds', [Sw.ElapsedMilliseconds]));
 Writeln(Format('Delete line with stream reader/writer - file 1Mb, %d iterations', [Iterations]));
 Sw := TStopwatch.StartNew;
 for Count := 1 to Iterations do
  DeleteLineWithStreamReaderAndWriter('c:\temp\text1Mb.txt', 'thislinewillbedeleted=');
 Sw.Stop;
 Writeln(Format('Elapsed time : %d milliseconds', [Sw.ElapsedMilliseconds]));
end;

procedure TimeDeleteLineWithStringList(Iterations : Integer);

var
  Count : Integer;
  Sw    : TStopWatch;

begin
 Writeln(Format('Delete line with TStringlist - file 10kb, %d iterations', [Iterations]));
 Sw := TStopwatch.StartNew;
 for Count := 1 to Iterations do
  DeleteLineWithStringList('c:\temp\text10kb.txt', 'thislinewillbedeleted=');
 Sw.Stop;
 Writeln(Format('Elapsed time : %d milliseconds', [Sw.ElapsedMilliseconds]));
 Writeln(Format('Delete line with TStringlist - file 1Mb, %d iterations', [Iterations]));
 Sw := TStopwatch.StartNew;
 for Count := 1 to Iterations do
  DeleteLineWithStringList('c:\temp\text1Mb.txt', 'thislinewillbedeleted=');
 Sw.Stop;
 Writeln(Format('Elapsed time : %d milliseconds', [Sw.ElapsedMilliseconds]));
end;

procedure TimeDeleteLineWithTextFile(Iterations : Integer);

var
  Count : Integer;
  Sw    : TStopWatch;

begin
 Writeln(Format('Delete line with text file - file 10kb, %d iterations', [Iterations]));
 Sw := TStopwatch.StartNew;
 for Count := 1 to Iterations do
  DeleteLineWithTextFile('c:\temp\text10kb.txt', 'thislinewillbedeleted=');
 Sw.Stop;
 Writeln(Format('Elapsed time : %d milliseconds', [Sw.ElapsedMilliseconds]));
 Writeln(Format('Delete line with text file - file 1Mb, %d iterations', [Iterations]));
 Sw := TStopwatch.StartNew;
 for Count := 1 to Iterations do
  DeleteLineWithTextFile('c:\temp\text1Mb.txt', 'thislinewillbedeleted=');
 Sw.Stop;
 Writeln(Format('Elapsed time : %d milliseconds', [Sw.ElapsedMilliseconds]));
end;

begin
  try
    TimeDeleteLineWithStringList(100);
    TimeDeleteLineWithStreamReaderAndWriter(100);
    TimeDeleteLineWithTextFile(100);
    Writeln('Press ENTER to quit');
    Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

出力:

Delete line with TStringlist - file 10kb, 100 iterations
Elapsed time : 188 milliseconds
Delete line with TStringlist - file 1Mb, 100 iterations
Elapsed time : 5137 milliseconds
Delete line with stream reader/writer - file 10kb, 100 iterations
Elapsed time : 456 milliseconds
Delete line with stream reader/writer - file 1Mb, 100 iterations
Elapsed time : 22382 milliseconds
Delete line with text file - file 10kb, 100 iterations
Elapsed time : 250 milliseconds
Delete line with text file - file 1Mb, 100 iterations
Elapsed time : 9656 milliseconds
Press ENTER to quit

ご覧のとおり、ここでは TStringList が勝者です。TStringList を使用できないため、結局のところ、TextFile は悪い選択ではありません...

PS:このコードは、入力ファイルを削除し、出力ファイルの名前を元のファイル名に変更する必要がある部分を省略しています

于 2012-12-06T09:16:48.317 に答える
5

ファイル全体を のようなコンテナーにロードしTStringListない場合、唯一の選択肢は次のとおりです。

  • 入力用にファイルを開く
  • 出力用に別のコピーを開く
  • ループを開始する
  • 入力ファイルからコンテンツを 1 行ずつ読み取る
  • 変更/削除する行に到達するまで、コンテンツを行ごとに出力ファイルに書き込みます
  • ループを壊す
  • 入力ファイルから入力行を読み取る
  • 変更された行を出力ファイルに書き込みます(または削除する行の書き込みをスキップします)
  • 新しいループを開始する
  • 入力内容の残りを 1 行ずつ読み取る
  • その入力の残りを出力ファイルに 1 行ずつ書き込みます。
  • ループを壊す
  • ファイルを閉じる

特定の質問に答えるには:

if N = UpperCase(Name) then begin
  //How to re-write this line?
  Break;
end;

新しい出力を 2 番目の (出力) ファイルに書き込みます。

if N = UpperCase(Name) then begin
  //How to delete this line?
  Break;
end;

WriteLn指定された行を 2 番目の (出力) ファイルに出力する をスキップするだけです。

「TStringListを使用したくない」という人為的な制限は、単純に次のことができる場合、タスクを複雑にします。

  • TStringList元のファイルを使用してロードしますLoadFromFile
  • インデックス、繰り返し、またはいずれかによって、変更する行を見つけます。IndexOf()
  • 行を直接変更するか、TStringList
  • を使用してコンテンツ全体を元のファイルに書き出しますTStringList.SaveToFile

これらの種類の操作を実行するために使用しないことがわかった唯一の理由はTStringList、ファイルサイズが の容量を超えているTStringList(決して起こらなかった) か、テキストであるが実際には「行」指向ではないファイルを処理するときです (たとえば、通常は非常に長い 1 行のテキストである EDI ファイルや、改行を含まないために非常に長い 1 行のテキストである XML ファイルなどです)。EDI や XML の場合でも、TStringList.

于 2012-12-06T04:13:37.113 に答える
3

基本的に、ファイルを単なるテキストファイルとして扱っていては、やりたいことができません。このようなファイルは、読み取り (最初からのみ)、書き込み (最初から新しいファイルを作成)、または最後から (既存のファイルに追加) が可能です。これらはランダム アクセス ファイルではありません。

一方、文字列タイプのファイルを定義することを検討したい場合があります。ファイル内の各レコードは文字列であり、このファイルにランダムにアクセスできます。問題は、どの文字列に対してどのレコードにアクセスするかを知ることです。

3 番目の可能性は、より構造化された INI ファイルを使用することです。セクション ヘッダーとは別に、これらは一連の文字列、key=value であり、キーに基づいてアクセスできます。

于 2012-12-06T04:14:10.600 に答える