8

我正在以二进制形式将文件加载到数组中,这似乎需要一段时间是否有更好更快更有效的方法来做到这一点。我正在使用类似的方法写回文件。

procedure openfile(fname:string);
var
    myfile: file;
    filesizevalue,i:integer;
begin
  assignfile(myfile,fname);
  filesizevalue:=GetFileSize(fname); //my method
  SetLength(dataarray, filesizevalue);
  i:=0;
  Reset(myFile, 1);
  while not Eof(myFile) do
    begin
      BlockRead(myfile,dataarray[i], 1);
      i:=i+1;
    end;
  CloseFile(myfile);
end;
4

6 回答 6

16

如果您真的想快速读取二进制文件,请让 Windows 担心缓冲 ;-) 使用Memory Mapped Files。使用它,您可以简单地将文件映射到一个内存位置,就像它是一个数组一样读取。

您的功能将变为:

procedure openfile(fname:string);
var
    InputFile: TMappedFile;
begin
  InputFile := TMappedFile.Create;
  try
    InputFile.MapFile(fname);
    SetLength(dataarray, InputFile.Size);
    Move(PByteArray(InputFile.Content)[0], Result[0], InputFile.Size);
  finally
    InputFile.Free;
  end;
end;

但我建议不要使用全局变量dataarray,而是将其作为 var 传递给参数,或者使用返回结果数组的函数。

procedure ReadBytesFromFile(const AFileName : String; var ADestination : TByteArray);
var
    InputFile : TMappedFile;
begin
  InputFile := TMappedFile.Create;
  try
    InputFile.MapFile(AFileName);
    SetLength(ADestination, InputFile.Size);
    Move(PByteArray(InputFile.Content)[0], ADestination[0], InputFile.Size);
  finally
    InputFile.Free;
  end;
end;

TMappedFile 来自我的文章Fast reading of files using Memory Mapping,本文还包含一个示例,说明如何将其用于更“高级”的二进制文件。

于 2009-01-19T08:17:38.727 回答
15

您通常不应该逐字节读取文件。使用具有较大值的 BlockRead(512 或 1024 通常是最好的)并使用它的返回值来找出读取了多少字节。

如果大小不是太大(并且您对 SetLength 的使用似乎支持这一点),您还可以使用一个 BlockRead 调用一次读取完整的文件。因此,修改您的方法,这将是:

AssignFile(myfile,fname);
filesizevalue := GetFileSize(fname);
Reset(myFile, 1);
SetLength(dataarray, filesizevalue);
BlockRead(myFile, dataarray[0], filesizevalue);
CloseFile(myfile);

也许您也可以将过程更改为名为 OpenAndReadFile 的布尔函数,如果无法打开或读取文件,则返回 false。

于 2009-01-18T20:03:02.810 回答
5

这取决于文件格式。如果它包含多个相同的记录,您可以决定创建该记录类型的文件。

例如:

type
  TMyRecord = record
    fieldA: integer;

    ..
  end;
  TMyFile = file of TMyRecord;

  const
    cBufLen = 100 * sizeof(TMyRecord);
  var
    file: TMyFile;
    i : Integer;

  begin
    AssignFile(file, filename);
    Reset(file);
    i := 0;
    try
      while not Eof(file) do begin
        BlockRead(file, dataarray[i], cBufLen);
        Inc(i, cBufLen);
      end;
    finally
      CloseFile(file);
    end;
  end;
于 2009-01-18T20:38:26.610 回答
3

如果文件足够长,以这种方式读取它需要相当长的时间,我会改用流。块读取会快很多,而且不用担心循环。像这样的东西:

procedure openfile(fname:string);
var
    myfile: TFileStream;
    filesizevalue:integer;
begin
  filesizevalue:=GetFileSize(fname); //my method
  SetLength(dataarray, filesizevalue);
  myFile := TFileStream.Create(fname);
  try
    myFile.seek(0, soFromBeginning);
    myFile.ReadBuffer(dataarray[0], filesizevalue);
  finally
     myFile.free;
  end;
end;

从您的代码中可以看出,您的记录大小为 1 个字节长。如果不是,则将读取行更改为:

  myFile.ReadBuffer(dataarray[0], filesizevalue * SIZE);

或类似的东西。

于 2009-01-19T05:49:39.040 回答
0

寻找缓冲的 TStream 后代。由于磁盘读取速度很快,它将使您的代码更快,但您可以轻松地循环通过缓冲区。有各种各样的,或者你可以自己写。

于 2009-01-19T09:32:23.473 回答
0

如果你觉得很头疼,你可以完全绕过 Win32 并调用 NT Native API 函数 ZwOpenFile() 在我的非正式测试中确实会减少一点。否则,我会使用上面的 Davy 的内存映射文件解决方案。

于 2009-01-19T16:41:34.557 回答