Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Построчное чтени большого текстового файла


Страницы:   1 2 3

IceBeerg ©   (2005-09-13 18:26:00) 

Подскажите выход из ситуации... Есть большой log файл (более 40 гигов), пытаюсь загружать его в StringList - читается не весь... Попробовал использовать FileStream, но не знаю как через него читать файл построчно... Цель - читать файл построчно, определять дату находящуюся в начале каждой строки и по этой дате выводить данные log'а. Строки файла НЕ одинаковой длинны...


KilkennyCat   (2005-09-14 08:58:00) [21]


> [29] Fay ©   (14.09.05 08:21)

то есть, он отслеживает изменение ТАКОГО лога?! когда ж он основным делом занят? :))


Fay   (2005-09-14 08:21:00) [22]

2 KilkennyCat ©   (14.09.05 8:00) [28]
Я ваще не спец по 1С Пр-е, но мне как-то запомнилось, что он матерится на изменение лога.


KilkennyCat   (2005-09-14 08:00:00) [23]


> 1C-у это не понравится.

а не надо им говорить.


Fay   (2005-09-14 06:51:00) [24]

2 Alexander Panov ©   (14.09.05 2:02) [20]
Хрень получится. К тому же, если не ошибаюсь, 1C-у это не понравится.
Лучше создать файл со смещениями строк.


Gydvin   (2005-09-14 06:27:00) [25]

Попробуй это. Только надо знать кол-во строк в файле.

function GrabLine(const AFileName: string; ALine: Integer): string;
var
  fs: TFileStream;
  buf: packed array[0..4095] of Char;
  bufRead: Integer;
  bufPos: PChar;
  lineStart: PChar;
  tmp: string;
begin
  fs := TFileStream.Create(AFileName, fmOpenRead);
  try
    Dec(ALine);
    bufRead := 0;
    bufPos := nil;

    { read the first line specially }
    if ALine = 0 then
    begin
      bufRead := fs.Read(buf, SizeOf(buf));
      if bufRead = 0 then
        raise Exception.Create('Line not found');
      bufPos := buf;
    end else
      while ALine > 0 do
      begin
        { read in a buffer }
        bufRead := fs.Read(buf, SizeOf(buf));
        if bufRead = 0 then
          raise Exception.Create('Line not found');
        bufPos := buf;
        while (bufRead > 0) and (ALine > 0) do
        begin
          if bufPos^ = #10 then
            Dec(ALine);
          Inc(bufPos);
          Dec(bufRead);
        end;
      end;
    { Found the beginning of the line at bufPos... scan for end.
     2 cases:
       1) we'll find it before the end of this buffer
       2) it'll go beyond this buffer and into n more buffers }
    lineStart := bufPos;
    while (bufRead > 0) and (bufPos^ <> #10) do
    begin
      Inc(bufPos);
      Dec(bufRead);
    end;
    { if bufRead is positive, we'll have found the end and we can leave. }
    SetString(Result, lineStart, bufPos - lineStart);
    { determine if there are more buffers to process }
    while bufRead = 0 do
    begin
      bufRead := fs.Read(buf, SizeOf(buf));
      lineStart := buf;
      bufPos := buf;
      while (bufRead > 0) and (bufPos^ <> #10) do
      begin
        Inc(bufPos);
        Dec(bufRead);
      end;
      SetString(tmp, lineStart, bufPos - lineStart);
      Result := Result + tmp;
    end;
  finally
    fs.Free;

  end;
  delete(Result,length(Result),1);
end;


Defunct   (2005-09-14 02:36:00) [26]

kaif ©   (14.09.05 02:28) [24]

Log файл может постоянно дописываться. Класс [18] предназначен для работы с постоянно открытым Log файлом, т.е. приложение ведет себе лог, а мы подчитываем себе в базу только новые записи при необходимости. Бить файл на много мелких imho абсолютно не нужно. Проще добавить в базу все что надо и стереть весь этот лог.


kaif   (2005-09-14 02:28:00) [27]

Я бы для начала разрезал этот файл на 400 файлов равной длины, как советует Panov.
И лазил бы лишь туда, куда мне интересно лазить. Например, по дате.
Читал бы с помощью TFileStream блоками по 32K.
Внутри блоков искал бы символ возврата каретки.
Если что-то полезное нашел бы в этих файлах - перегнал бы в базу данных, а все бесполезное выкинул бы к чертям.


Defunct   (2005-09-14 02:25:00) [28]

Defunct ©   (14.09.05 02:12) [21]

допустил пару ошибок в [21]:

function T MyLogReader.GetString: String;
with TMyLogReader .Createdo


Defunct   (2005-09-14 02:22:00) [29]

Работает этот LogReaded так:

1. читает порцию данных из стрима, которая определяется константой lrBactchSize в байтах. (оптимальнее всего ~1Mb) в TStrings
2. выкусывает обрывки строк из TStrings, и корректирует абсолютную позицию в файле для чтения следующей порции строк.
3. Ключевой метод - ReadString, последовательно выдает все строки открытого лог файла.
4. Читать лог файл можно с определенной позиции, которая определяется при задании "последней прочитанной строки" (свойство LastLoadedString), имеется два способа поиска позиции - с начала файла и с конца, задается свойством ReversLookup. (поиск с конца значительно ускоряет процесс поиска позиции искомой строки, если искомая строка находится ближе к концу лог файла).


Defunct   (2005-09-14 02:12:00) [30]

Пример применения:

   TMyLogReader = class( TLogReader)
   public
      function GetString: String;
      constructor Create;override;
   end;

constructor TMyLogReader.Create;
begin
 inherited;
 fDontCheckMask := True;
end;

function TMailLogReader.GetString: TMailRecord;
var
  BackupLastString : String;
begin
  BackupLastString := fLastString;
  Result := ReadString;
  if  Result = '' then
     fLastString := BackupLastString
end;


Для чтения с начала:

var
  LLS : String;

 ...
 with TMyLogReader do
 try
     ReportBox := Memo1;
     ReverseLookup := False;
     FileName := 'some.log'
     LastLoadedString := '';
     
     while not Eof do
     begin
         LLS := GetString;
         ...
     end;
     <Новая последняя прочитанная строка> := LastString;
 finally
     free;
 end;


Для добавления новых записей с определенной позиции:

var
  LLS : String;

 ...
 with TMyLogReader do
 try
     ReportBox := Memo1;
     ReverseLookup := True;
     FileName := 'some.log'
     LastLoadedString := <Сохраненная последняя прочитанная строка>;
     
     while not Eof do
     begin
         LLS := GetString;
         ...
     end;
     <Новая последняя прочитанная строка> := LastString;
 finally
     free;
 end;


Alexander Panov   (2005-09-14 02:02:00) [31]

IceBeerg ©   (13.09.05 18:26)

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


Defunct   (2005-09-14 01:50:00) [32]

Продолжение..

procedure TLogReader.SetLastLoadedString(const Value: String);
var
 S: String;
begin
 if Value <> '' then
   try
      Report('setting LLS to locate correct stream position');
      Report('incoming LLS = '+Value);
      fSearching := True;
      if fReverseLookup then BatchReadFromEnd;

      fLastLoadedString := Value;  // будем искать эту строку в файле
      if CanRead then
         begin
            S := ReadString;
            while (Pos(fLastLoadedString, S)=0) and (not EOf) do
               S := ReadString;
            fStartPos := fLastPos;
         end;
      Report('LLS detected at stream postion = ' + IntToStr( fLastPos ) );
      Report('LLS`s batch position = ' + IntToStr( fBatchPos ) );
   finally
      fSearching := false;
   end
 else
   begin
      Report('there is no selected LLS, stream will be read from begin');
   end;
end;

function TLogReader.GetEndPos: Int64;
begin
  if Assigned( fStream ) then
     Result := fStream.Size
  else
     Result := 0;
end;

function TLogReader.GetPercentDone: integer;
begin
  Result := 100;
  if Assigned( fStream ) then
     if EndPosition - StartPosition > 0 then
     begin
       Result := Round( Result * (fStream.Position - StartPosition)/
                     (EndPosition - StartPosition) )
     end
end;

destructor TLogReader.Destroy;
begin
 Report('exiting log reader');
 FreeAndNil( fStream );
 FreeAndNil( fStrings );
 inherited;
end;


Defunct   (2005-09-14 01:49:00) [33]

> IceBeerg

   TLogReader = class
   private
      fFileName   : String;
      fCanRead    : boolean;
      fLastString : String;
      fStream     : TFileStream;
      fStrings    : TStrings;
      fEof        : boolean;
      fErrors     : integer; // Ошибочных строк
      fStartPos   : Int64;   // Позиция начала загрузки
      fLastPos    : Int64;   // Последняя прочитанная позиция
      fNextPos    : Int64;   // Позиция для чтения следующей пачки
      fBatchPos   : Integer; // Позиция внутри порции прочитанных строк
      fLastLoadedString: String;
      fDontCheckMask : boolean;
      fMask          : String;
      fMemo          : TMemo;
      fReverseLookup : boolean;
      fSearching     : boolean;
      fPosFromEnd    : Int64;

      procedure BatchReadFromEnd;
      procedure BatchRead; // читает порцию из лог файла в fStrings
      procedure SetFileName(const Value: String);
      function  ReadString: String;
      procedure SetLastLoadedString(const Value: String);
      function  GetEndPos: Int64;
      function  GetPercentDone: Integer;
      procedure SetMemo(const Value: TMemo);
      procedure Report(const S: String );
      procedure SetReverse(const Value: boolean);
   public
      property FileName: String read fFileName write SetFileName;

      property LastLoadedString:String read fLastLoadedString
               write SetLastLoadedString; // Для инициализации продолжения
      property CanRead: boolean read fCanRead;
      property LastString: String read fLastString;
      property Eof:boolean read fEof;
      property Strings:TStrings read fStrings;
      property StartPosition:Int64 read fStartPos;
      property EndPosition:Int64 read GetEndPos;
      property Progress:Integer read GetPercentDone;
      property ReportBox: TMemo read fMemo write SetMemo;
      property ReverseLookup : boolean read fReverseLookup write SetReverse;

      constructor Create;dynamic;
      destructor Destroy;override;
   end;

{ TLogReader }

constructor TLogReader.Create;
begin
  fFileName := '';
  fMemo := nil;
  fLastString := '';
  fLastLoadedString := '';
  fCanRead := false;
  fStream := nil;
  fEof := True;
  fDontCheckMask := false;
  fStrings := TStringList.Create;
end;

procedure TLogReader.Report(const S: String);
begin
 if Assigned( fMemo ) then
    fMemo.Lines.Add( S );
end;

procedure TLogReader.SetMemo(const Value: TMemo);
begin
 fMemo := Value;
 fMemo.Lines.Clear;
end;

procedure TLogReader.SetReverse(const Value: boolean);
begin
 fReverseLookup := Value;
 if fReverseLookup then
    Report('setting lookup reverse=TRUE (searching from END of stream)')
 else
    Report('setting lookup reverse=FALSE (searching from BEGIN of stream');
end;

procedure TLogReader.SetFileName(const Value: String);
begin
 Report('setting log file name = ' + Value );
 fCanRead := FileExists( Value );
 if Assigned(fStream) then
    FreeAndNil( fStream );

 if CanRead then
    begin
       fFileName := Value;
       fStream := TFileStream.Create( fFileName, fmOpenRead or fmShareDenyNone );
       fNextPos := 0;
       fStartPos := 0;
       fLastPos := 0;
       fStrings.Clear;
       fEof := fStream.Size = 0;
       fPosFromEnd := fStream.Size;
       BatchRead;
       Report('Assigned filename has open successfully');
    end
 else
    begin
       fFileName := '';
       Report('Assigned filename not found, reseting filename');
    end;
end;

procedure TLogReader.BatchReadFromEnd;
var
  S : String;
  i : integer;

  function GetBatchPos:Int64;
  begin
     Result := fPosFromEnd - lrBatchSize;
     if Result < 0 then
        Result := 0;
  end;

begin
  Report('Trying to do batch read (direction=right to left)');
  fNextPos := fPosFromEnd;
  Report('Current batch position ='+ IntToStr( fNextPos ));
  fPosFromEnd := GetBatchPos;
  fLastPos := fPosFromEnd;
  fStream.Position := fPosFromEnd;

  SetLength( S, lrBatchSize );
  SetLength( S, fStream.Read( S[1], lrBatchSize ) );

  for i := 1 to Length(S) do
     if S[i] = #10 then break;

  fPosFromEnd := fPosFromEnd + i;

  fStrings.Text := Copy(S, i + 1, Length(S) - i) ;
  Report('batch read (direction=right to left) success, alloc. StrCount='+
          IntToStr( fStrings.Count ) );
  fBatchPos := 0;
end;

procedure TLogReader.BatchRead;
var
  S : String;
  i : integer;
begin
  if CanRead then
  try
     Report('Trying to do batch read');
     fStream.Position := fNextPos;
     Report('Current batch position ='+ IntToStr( fNextPos ));
     fLastPos := fNextPos;
     SetLength( S, lrBatchSize );
     SetLength( S, fStream.Read( S[1], lrBatchSize ) );

     for i := Length( S ) - 3 downto 1 do
         if S[i] = #10 then break;

     fNextPos := fNextPos + i;

     if i > 2 then
       fStrings.Text := Copy( S, 1, i-2)
     else
       fStrings.Text := Copy( S, 1, i);

     Report('batch read success, allocated stringcount='+IntToStr( fStrings.Count ) );

     fBatchPos := 0;
  except
     on E:Exception do ShowException('BatchRead', ClassName, E)
  end;
end;

function TLogReader.ReadString;
begin
 Result := '';
 if CanRead and (fStrings.Count > 0) then
 begin
    if fBatchPos < fStrings.Count then
       begin
          Result := fStrings.Strings[fBatchPos];
          inc(fBatchPos);
          if not fDontCheckMask then
          if (pos(fMask, Result) = 0) and (fBatchPos < fStrings.Count) then
          begin
             inc( fErrors );
             Result := ReadString;
          end;
       end
    else
       begin
          if fSearching and fReverseLookup then
             BatchReadFromEnd
          else
             BatchRead;
          Result := ReadString;
       end;
 end;

 fEof := Length(Result) = 0;
 if not fEof then
    fLastString := Result

end;


Fay   (2005-09-13 23:38:00) [34]

2 Игорь Шевченко ©   (13.09.05 18:52) [8]
>> Быстрее все равно не сделаешь
Спорим? 8)


Anatoly Podgoretsky   (2005-09-13 19:30:00) [35]

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


Anatoly Podgoretsky   (2005-09-13 19:28:00) [36]

PVOzerski ©   (13.09.05 19:03) [13]
Ничего страшного, начиная с Дельфи 6 ReadLn более благосклонен к разделителям строк, по крайней мере Юникс формат понимает, насчет Макинтош неуверен, и еще более не уверен насчет извращеного хакерского формата. LFCR а учитывая как написан 1С там может быть и такое и даже все четыре варианта одновременно.


Anatoly Podgoretsky   (2005-09-13 19:28:00) [37]

PVOzerski ©   (13.09.05 18:59) [12]
Проблема поиска решается буферизацией, как это сделано в ReadLn


PVOzerski   (2005-09-13 19:03:00) [38]

2Игорь Шевченко:
Я не знаю формат лога 1С - может, там не #13#10 :)


PVOzerski   (2005-09-13 18:59:00) [39]

ReadFile, SetFilePointer. Только проблема поиска знаков перевода каретки остается. На самом деле, TFileStream использует внутри себя эти же функции.


Игорь Шевченко   (2005-09-13 18:59:00) [40]

IceBeerg ©   (13.09.05 18:55) [10]


>
> никакой, сам лажанулся


Да если бы...Не ты первый в этом форуме :)


> а все же хотелось бы, но видимо никак


А главное - зачем ? Если лог копился два года, то подождать какое-то раумное время всяко можно ?


> в даном моменте то же прийдется использовать метод предложенный
> PVOzerski по поводу блочного чтения...


И зачем изобретать readln, которая делает абсолютно все то же самое ? :)


Страницы:   1 2 3

Наверх

Титульная страница Поиск, карта сайта Написать письмо