如何有效地阅读Delphi中许多文件的最初几行

时间:2011-01-30 20:39:08

标签: delphi file-io large-files

我的程序中有一个“查找文件”功能,可以找到程序读取的带有.ged后缀的文本文件。我在类似于资源管理器的窗口中显示找到的结果,如下所示:

enter image description here

我使用标准的FindFirst / FindNext方法,这非常有效。上面显示的584个文件可在几秒钟内找到并显示。

我现在要做的是在显示屏上添加两列,显示每个文件中包含的“Source”和“Version”。此信息通常位于每个文件的前10行内,如下所示:

1 SOUR FTM
2 VERS Family Tree Maker (20.0.0.368)

现在我自己解析这个问题没有问题,这不是我要问的问题。

我需要帮助的是如何最快速地从这些文件加载​​前10行,以便我可以解析它们。

我试过做一个StringList.LoadFromFile,但加载大文件需要花费太多时间,例如1 MB以上的文件。

由于我只需要前10行左右,我最好如何获得它们?

我正在使用Delphi 2009,我的输入文件可能是也可能不是Unicode,所以这需要适用于任何编码。


跟进:谢谢Antonio,

我最终这样做了,效果很好:

var
  CurFileStream: TStream;
  Buffer: TBytes;
  Value: string;
  Encoding: TEncoding;

try
  CurFileStream := TFileStream.Create(folder + FileName, fmOpenRead);
  SetLength(Buffer, 256);
  CurFileStream.Read(Buffer[0], 256);
  TEncoding.GetBufferEncoding(Buffer, Encoding);
  Value := Encoding.GetString(Buffer);
  ...
  (parse through Value to get what I want)
  ...
finally
  CurFileStream.Free;
end;

5 个答案:

答案 0 :(得分:14)

使用TFileStream并使用Read方法读取所需的字节数。以下是读取位图信息的示例,该位图信息也存储在文件的开头。

http://www.delphidabbler.com/tips/19

答案 1 :(得分:4)

只需自己打开文件进行块读取(不使用TStringList内置功能),然后读取文件的第一个块,然后你就可以将该块加载到带有strings.SetText()的字符串列表中(如果你是如果使用流加载块,则使用块函数)或简单地使用strings.LoadFromStream()。

我个人只是使​​用FileRead / FileWrite块函数,并将块加载到缓冲区中。你也可以使用similair winapi函数,但这只是无用的代码。

操作系统读取块中的文件,这些块几乎在任何平台/文件系统上都至少有512字节,所以你可以先读取512个字节(并希望你得到所有10行,如果你的行通常足够短,这将是真的)。这(实际上)与读取100或200字节一样快。

然后,如果您注意到您的字符串对象只有少于10行,则只需读取下一个512字节块并尝试再次解析。 (或者只是使用1024,2048等块,在许多系统上它可能会快512块,因为文件系统簇大小通常大于512字节)。

PS。此外,在winapi文件函数(CreateFile等)中使用线程或异步功能,您可以异步加载文件中的数据,而其他应用程序可以正常工作。具体来说,在读取大目录期间,接口不会冻结。

这将使您的信息加载速度更快,(因为文件列表将直接加载,然后几毫秒后其余信息将会出现),而实际上并没有提高实际读取速度。

只有在尝试过其他方法并且您觉得需要额外提升时才这样做。

答案 2 :(得分:3)

您可以使用TStreamReader来读取任何TStream对象中的各个行,例如TFileStream。对于更快的文件I / O,您可以使用TCustomMemoryStream的内存映射视图。

答案 3 :(得分:2)

好的,我删除了我的第一个答案。使用上面的Remy的第一个建议,我再次尝试使用内置的东西。我不喜欢这里是你必须创建和释放两个对象。我想我会把自己的课程包起来:

var
  fs:TFileStream;
  tr:TTextReader;
  filename:String;
begin
  filename :=  'c:\temp\textFileUtf8.txt';
  fs := TFileStream.Create(filename, fmOpenRead);
  tr := TStreamReader.Create(fs);
  try
      Memo1.Lines.Add( tr.ReadLine );

  finally
    tr.Free;
    fs.Free;
  end;   
end;

如果有人对我以前所拥有的内容感兴趣,那么就会遇到不使用unicode文件的问题。

答案 4 :(得分:0)

有时候oldschool pascal stylee也不是那么糟糕。 即使非oo文件访问似乎不再受欢迎,ReadLn(F,xxx)在像你这样的情况下仍然可以正常工作。

下面的代码将信息(文件名,来源和版本)加载到TDictionary中,以便您可以轻松查找,或者您可以在虚拟模式下使用列表视图,并在此列表中查找内容ondata甚至开火。

警告:以下代码不适用于unicode。

program Project101;
{$APPTYPE CONSOLE}

uses
  IoUtils, Generics.Collections, SysUtils;

type
  TFileInfo=record
    FileName,
    Source,
    Version:String;
  end;

function LoadFileInfo(var aFileInfo:TFileInfo):Boolean;
var
  F:TextFile;
begin
  Result := False;
  AssignFile(F,aFileInfo.FileName);
  {$I-}
  Reset(F);
  {$I+}
  if IOResult = 0 then
  begin
    ReadLn(F,aFileInfo.Source);
    ReadLn(F,aFileInfo.Version);
    CloseFile(F);
    Exit(True)
  end
  else
    WriteLn('Could not open ', aFileInfo.FileName);
end;

var
  FileInfo:TFileInfo;
  Files:TDictionary<string,TFileInfo>;
  S:String;
begin
  Files := TDictionary<string,TFileInfo>.Create;
  try
    for S in TDirectory.GetFiles('h:\WINDOWS\system32','*.xml') do
    begin
      WriteLn(S);
      FileInfo.FileName := S;
      if LoadFileInfo(FileInfo) then
        Files.Add(S,FileInfo);
    end;

    // showing file information...
    for FileInfo in Files.Values do
      WriteLn(FileInfo.Source, ' ',FileInfo.Version);
  finally
    Files.Free
  end;
  WriteLn;
  WriteLn('Done. Press any key to quit . . .');
  ReadLn;
end.