我使用的是Delphi 2007,我有一个可以通过内部网络从多个地方读取日志文件并显示异常的应用程序。这些目录有时包含数千个日志文件。应用程序有一个选项,可以只从最近n天的位读取日志文件,它也可以在任何日期时间范围内。
问题是第一次读取日志目录时可能非常慢(几分钟)。第二次要快得多。
我想知道如何优化我的代码以尽可能快地读取日志文件?我使用vCurrentFile:TStringList将文件存储在内存中。这是从FileStream更新的,因为我认为这样更快。
以下是一些代码:
刷新:读取日志文件的主循环
// In this function the logfiles are searched for exceptions. If a exception is found it is stored in a object.
// The exceptions are then shown in the grid
procedure TfrmMain.Refresh;
var
FileData : TSearchRec; // Used for the file searching. Contains data of the file
vPos, i, PathIndex : Integer;
vCurrentFile: TStringList;
vDate: TDateTime;
vFileStream: TFileStream;
begin
tvMain.DataController.RecordCount := 0;
vCurrentFile := TStringList.Create;
memCallStack.Clear;
try
for PathIndex := 0 to fPathList.Count - 1 do // Loop 0. This loops until all directories are searched through
begin
if (FindFirst (fPathList[PathIndex] + '\*.log', faAnyFile, FileData) = 0) then
repeat // Loop 1. This loops while there are .log files in Folder (CurrentPath)
vDate := FileDateToDateTime(FileData.Time);
if chkLogNames.Items[PathIndex].Checked and FileDateInInterval(vDate) then
begin
tvMain.BeginUpdate; // To speed up the grid - delays the guichange until EndUpdate
fPathPlusFile := fPathList[PathIndex] + '\' + FileData.Name;
vFileStream := TFileStream.Create(fPathPlusFile, fmShareDenyNone);
vCurrentFile.LoadFromStream(vFileStream);
fUser := FindDataInRow(vCurrentFile[0], 'User'); // FindData Returns the string after 'User' until ' '
fComputer := FindDataInRow(vCurrentFile[0], 'Computer'); // FindData Returns the string after 'Computer' until ' '
Application.ProcessMessages; // Give some priority to the User Interface
if not CancelForm.IsCanceled then
begin
if rdException.Checked then
for i := 0 to vCurrentFile.Count - 1 do
begin
vPos := AnsiPos(MainExceptionToFind, vCurrentFile[i]);
if vPos > 0 then
UpdateView(vCurrentFile[i], i, MainException);
vPos := AnsiPos(ExceptionHintToFind, vCurrentFile[i]);
if vPos > 0 then
UpdateView(vCurrentFile[i], i, HintException);
end
else if rdOtherText.Checked then
for i := 0 to vCurrentFile.Count - 1 do
begin
vPos := AnsiPos(txtTextToSearch.Text, vCurrentFile[i]);
if vPos > 0 then
UpdateView(vCurrentFile[i], i, TextSearch)
end
end;
vFileStream.Destroy;
tvMain.EndUpdate; // Now the Gui can be updated
end;
until(FindNext(FileData) <> 0) or (CancelForm.IsCanceled); // End Loop 1
end; // End Loop 0
finally
FreeAndNil(vCurrentFile);
end;
end;
UpdateView方法:在DisplayGrid中添加一行
{: Update the grid with one exception}
procedure TfrmMain.UpdateView(aLine: string; const aIndex, aType: Integer);
var
vExceptionText: String;
vDate: TDateTime;
begin
if ExceptionDateInInterval(aLine, vDate) then // Parse the date from the beginning of date
begin
if aType = MainException then
vExceptionText := 'Exception'
else if aType = HintException then
vExceptionText := 'Exception Hint'
else if aType = TextSearch then
vExceptionText := 'Text Search';
SetRow(aIndex, vDate, ExtractFilePath(fPathPlusFile), ExtractFileName(fPathPlusFile), fUser, fComputer, aLine, vExceptionText);
end;
end;
确定行是否在日期范围内的方法:
{: This compare exact exception time against the filters
@desc 2 cases: 1. Last n days
2. From - to range}
function TfrmMain.ExceptionDateInInterval(var aTestLine: String; out aDateTime: TDateTime): Boolean;
var
vtmpDate, vTmpTime: String;
vDate, vTime: TDateTime;
vIndex: Integer;
begin
aDateTime := 0;
vtmpDate := Copy(aTestLine, 0, 8);
vTmpTime := Copy(aTestLine, 10, 9);
Insert(DateSeparator, vtmpDate, 5);
Insert(DateSeparator, vtmpDate, 8);
if TryStrToDate(vtmpDate, vDate, fFormatSetting) and TryStrToTime(vTmpTime, vTime) then
aDateTime := vDate + vTime;
Result := (rdLatest.Checked and (aDateTime >= (Now - spnDateLast.Value))) or
(rdInterval.Checked and (aDateTime>= dtpDateFrom.Date) and (aDateTime <= dtpDateTo.Date));
if Result then
begin
vIndex := AnsiPos(']', aTestLine);
if vIndex > 0 then
Delete(aTestLine, 1, vIndex + 1);
end;
end;
测试文件数据是否在范围内:
{: Returns true if the logtime is within filters range
@desc Purpose is to sort out logfiles that are no idea to parse (wrong day)}
function TfrmMain.FileDateInInterval(aFileTimeStamp: TDate): Boolean;
begin
Result := (rdLatest.Checked and (Int(aFileTimeStamp) >= Int(Now - spnDateLast.Value))) or
(rdInterval.Checked and (Int(aFileTimeStamp) >= Int(dtpDateFrom.Date)) and (Int(aFileTimeStamp) <= Int(dtpDateTo.Date)));
end;
2条答案
按热度按时间k4aesqcs1#
问题不在于您的逻辑,而在于底层的文件系统。
当您将许多文件放在一个目录中时,大多数文件系统会变得非常慢。这在FAT中非常糟糕,但NTFS也会受到影响,特别是在一个目录中有数千个文件的情况下。
您所能做的最好的就是重新组织这些目录结构,例如按年龄。
则每个目录中最多有几个100个文件。
--耶伦
rhfm7lfc2#
你想要多快?如果你想真的很快,那么你需要使用Windows网络以外的东西来读取文件。原因是,如果您想要读取日志文件的最后一行(或自上次读取以来的所有行),则需要再次读取整个文件。
在您的问题中,您说问题是列举您的目录列表的速度很慢。这是你的第一个瓶颈。如果您想要真正快速,那么您需要切换到HTTP,或者向存储日志文件的机器添加某种类型的日志服务器。
使用HTTP的优点是,您可以执行范围请求,并且只获得自上次请求以来添加的日志文件的新行。这将真正提高性能,因为您传输的数据更少(尤其是在启用HTTP压缩的情况下),而且您在客户端要处理的数据也更少。
如果添加某种类型的日志服务器,则该服务器可以在服务器端执行处理,在服务器端它对数据具有本机访问权限,并且只返回日期范围内的行。要做到这一点,一种简单的方法可能是将您的日志放入某种类型的SQL数据库,然后对其运行查询。
那么,你想开多快?