Подтвердить что ты не робот

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

Ответ 3

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

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;

Ответ 4

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

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

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

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

Ответ 5

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

Ответ 6

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

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

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


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

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

Ответ 7

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

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