![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Построчное чтени большого текстового файла
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, которая делает абсолютно все то же самое ? :)