2

我正在使用 Delphi 中的文本文件,我不希望使用通过字符串列表加载/保存的方法。我打算维护一个打开的文件流,在那里我读取和写入我的数据,将大量数据保存在硬盘上而不是内存中。我有将新行写入文本文件并读取它们的简单概念,但是在修改和删除它们时,我找不到任何好的资源。

此文件中的每一行都包含一个名称和等号,其余为数据。例如,SOMEUNIQUENAME=SomeStringValue。我打算在线程内保持文件打开一段时间。此线程执行传入请求以获取、设置或删除某些数据字段。我在一个循环中使用WriteLnand来评估. 以下是我如何读取数据的示例:ReadLnEOF

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 种方法:

  • 字符串列表
  • Streamreader/StreamWriter
  • 文本文件

所有方法都使用 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,您唯一的选择是:

  • 打开文件进行输入
  • 打开单独的副本进行输出
  • 开始一个循环
  • 从输入文件中逐行读取内容
  • 将内容逐行写入输出文件,直到到达要更改/删除的行
  • 打破循环
  • 从输入文件中读取输入行
  • 将更改的行(或跳过写入要删除的行)写入输出文件
  • 开始一个新的循环
  • 逐行读取输入内容的其余部分
  • 将该输入的其余部分逐行写入输出文件
  • 打破循环
  • 关闭文件

因此,要回答您的具体问题:

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

将新输出写入第二个(输出)文件。

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

只需跳过将WriteLn指示行输出到第二个(输出)文件的那个。

当您可以简单地执行以下操作时,您对“我不想使用 TStringList”的人为限制只会使您的任务复杂化:

  • 将原始文件加载到TStringList使用中LoadFromFile
  • 通过索引、迭代或IndexOf()
  • 通过直接更改它来修改行,或者从TStringList
  • 使用将整个内容写入原始文件TStringList.SaveToFile

我发现用于TStringList执行此类操作的唯一原因是文件大小超过了 a 的容量TStringList(从未发生过),或者在处理文本文件但不是真正面向“行”的文件时(例如,EDI 文件通常是非常长的单行文本,或 XML 文件可能不包含换行符,因此也是非常长的单行文本)。但是,即使在 EDI 或 XML 的情况下,也经常将它们加载到TStringList. 中,转换为基于行的格式(插入换行符或其他),然后从字符串列表中进行检索。

于 2012-12-06T04:13:37.113 回答
3

基本上,如果您将文件视为简单的文本文件,您将无法做您想做的事情。可以读取(仅从头开始)或写入(从头开始,从而创建新文件)或从结尾(附加到现有文件)读取此类文件。它们不是随机访问文件。

另一方面,您可能需要考虑定义一个字符串类型的文件:文件中的每条记录都是一个字符串,您可以以随机方式访问该文件。然后问题就在于知道要访问哪个记录以访问哪个字符串。

第三种可能性是使用更结构化的 INI 文件,听起来更适合您的目的。除了section header之外,它们是一系列字符串,key=value,可以根据key进行访问。

于 2012-12-06T04:14:10.600 回答