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

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

12
задан Ian Boyd 12 May 2010 в 02:48
поделиться

6 ответов

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

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

15
ответ дан 2 December 2019 в 06:07
поделиться

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

3
ответ дан 2 December 2019 в 06:07
поделиться

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

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

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

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

2
ответ дан 2 December 2019 в 06:07
поделиться

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

2
ответ дан 2 December 2019 в 06:07
поделиться

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

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

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


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

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

0
ответ дан 2 December 2019 в 06:07
поделиться

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

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
ответ дан 2 December 2019 в 06:07
поделиться
Другие вопросы по тегам:

Похожие вопросы: