Delphi: альтернатива использованию Reset / ReadLn для чтения текстовых файлов - PullRequest
12 голосов
/ 12 мая 2010

я хочу обрабатывать текстовый файл построчно.В старые времена я загружал файл в 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).

Я попытался использовать собственные и рекомендуемые процедуры ввода-вывода файлов, предоставляемые Delphi:

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, где я могу читать файл построчно, без блокировки совместного использования?

Я бы предпочел не входить непосредственно в Win32 CreateFile / ReadFile, а иметь дело с распределением буферов и обнаружением CR, LF, CRLF.

я думал офайлы, отображаемые в память, но есть проблема, если весь файл не помещается (отображается) в виртуальную память, и приходится отображать представления (части) файла за один раз.Начинает становиться уродливым.

я просто хочу Reset с fmShareDenyNone!

Ответы [ 7 ]

15 голосов
/ 12 мая 2010

В последних версиях Delphi вы можете использовать TStreamReader. Создайте его с вашим файловым потоком, а затем вызовите его ReadLine метод (унаследованный от TTextReader).

Опция для всех версий Delphi состоит в том, чтобы использовать Peter Below's StreamIO unit , что дает вам AssignStream. Он работает так же, как AssignFile, но для потоков вместо имен файлов. После того, как вы использовали эту функцию, чтобы связать поток с переменной TextFile, вы можете вызвать ReadLn и другие функции ввода-вывода для него, как и для любого другого файла.

3 голосов
/ 02 июня 2010

Вы можете использовать этот пример кода:

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;
3 голосов
/ 12 мая 2010

Если вам нужна поддержка ANSI и Unicode в более старых Delphis, вы можете использовать мой GpTextFile или GpTextStream .

2 голосов
/ 12 мая 2010

Как кажется, переменная FileMode недопустима для текстовых файлов, но мои тесты показали, что многократное чтение из файла не является проблемой. Вы не упомянули об этом в своем вопросе, но если вы не собираетесь писать в текстовый файл, пока он читается, вам должно быть хорошо.

2 голосов
/ 12 мая 2010

Я использую TFileStream, но я буферизую ввод в довольно большие блоки (например, по несколько мегабайт каждый) и считываю и обрабатываю один блок за раз. Таким образом, мне не нужно загружать весь файл сразу.

Это работает довольно быстро, даже для больших файлов.

У меня есть индикатор прогресса. Когда я загружаю каждый блок, я увеличиваю его на долю файла, который был дополнительно загружен.

Чтение одной строки за раз, без какой-либо буферизации, просто слишком медленно для больших файлов.

0 голосов
/ 18 января 2012

У меня была такая же проблема несколько лет назад, особенно проблема блокировки файла. Я использовал низкоуровневый файл чтения из шеллапи. Я знаю, что вопрос старый, так как мой ответ (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;

Для меня улучшение скорости оказалось значительным.

0 голосов
/ 12 мая 2010

Почему бы просто не прочитать строки файла непосредственно из самого TFileStream по одной за раз?

т.е. (в псевдокоде):

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


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

Одна проблема, с которой вы можете столкнуться, заключается в том, что iirc TFileStream не буферизуется, поэтому производительность над большим файлом будет ниже оптимальной. Однако существует ряд решений проблемы небуферизованных потоков, , включая этот , которые вы, возможно, захотите изучить, если этот подход решит вашу первоначальную проблему.

...