📄 unit1.pas
字号:
{
演示两种方法来遍历文件
两个函数:
EnumFileInQueue 队列遍历
EnumFileInRecursion 递归遍历
版本:v1.0
作者:cenjoy 温校宏
邮箱:cenjoyer@163.com
说明:
看见很多遍历文件的文章都是用递归实现,可是
当子目录非常非常多时,就很容易堆栈溢出.
而使用队列的方法就不会出现这种问题.
不过使用队列这种方法也不是我想出来的,
我从书上看到的,因为发现还有很多人不知道,
所以把它贡献出来.
未解决问题:
1.如何统计遍历的时间和搜索结果
(如何知道用CreateThread创建的线程何时结束)
2.你来说吧~~~~
如果你能解决这些问题,希望你能发一份给我!!
}
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Contnrs, FileCtrl, ExtCtrls;
type
TForm1 = class(TForm)
Memo1: TMemo;
Button1: TButton;
Button2: TButton;
Button3: TButton;
DriveComboBox1: TDriveComboBox;
Label1: TLabel;
Bevel1: TBevel;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
private
{ Private declarations }
hThreadHandle1:THandle;
hThreadHandle2:THandle;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{用队列的方法遍历文件}
function EnumFileInQueue(path:PChar):Longint;stdcall;
var
searchRec:TSearchRec;
found:Integer;
tmpStr:String;
curDir:PChar;
dirs:TQueue;
begin
Result:=0;//查找结果(文件数)
dirs:=TQueue.Create;//创建目录队列
dirs.Push(path);//将起始搜索路径入队
curDir:=dirs.Pop;//出队
{开始遍历,直至队列为空(即没有目录需要遍历)}
while (curDir<> nil) do
begin
//加上搜索后缀,得到类似'c:\*.*' 、'c:\windows\*.*'的搜索路径
tmpStr:=StrPas(curDir)+'\*.*';
//在当前目录查找第一个文件、子目录
found:=FindFirst(tmpStr,faAnyFile,searchRec);
while found=0 do
//找到了一个文件或目录后
begin
//如果找到的是个目录
if (searchRec.Attr and faDirectory)<>0 then
begin
{在搜索非根目录(C:\、D:\)下的子目录时会出现'.','..'的"虚拟目录"
大概是表示上层目录和下层目录吧。。。要过滤掉才可以}
if (searchRec.Name <> '.') and (searchRec.Name <> '..') then
begin
{由于查找到的子目录只有个目录名,所以要添上上层目录的路径
searchRec.Name = 'Windows';
tmpStr:='c:\Windows';
加个断点就一清二楚了
}
tmpStr:=StrPas(curDir)+'\'+searchRec.Name;
{将搜索到的目录入队。让它先晾着。
因为TQueue里面的数据只能是指针,所以要把string转换为PChar
同时使用StrNew函数重新申请一个空间存入数据,否则会使已经进
入队列的指针指向不存在或不正确的数据(tmpStr是局部变量)。}
dirs.Push(StrNew(PChar(tmpStr)));
end;
end
//如果找到的是个文件
else begin
{Result记录着搜索到的文件数。可是我是用CreateThread创建线程
来调用函数的,不知道怎么得到这个返回值。。。我不想用全局变量}
Result:=Result+1;
//把找到的文件加到Memo控件
Form1.Memo1.Lines.Add(StrPas(curDir)+'\'+searchRec.Name);
end;
//查找下一个文件或目录
found:=FindNext(searchRec);
end;
{当前目录找到后,如果队列中没有数据,则表示全部找到了;
否则就是还有子目录未查找,取一个出来继续查找。}
if dirs.Count > 0 then
curDir:=dirs.Pop
else
curDir:=nil;
end;
//释放资源
dirs.Free;
FindClose(searchRec);
end;
{用递归的方法遍历文件}
function EnumFileInRecursion(path:PChar):Longint;stdcall;
var
searchRec:TSearchRec;
found:Integer;
tmpStr:String;
begin
Result:=0; //查找结果(文件数)
//加上搜索后缀,得到类似'c:\*.*' 、'c:\windows\*.*'的搜索路径
tmpStr:=StrPas(path)+'\*.*';
//在当前目录查找第一个文件、子目录
found:=FindFirst(tmpStr,faAnyFile,searchRec);
while found=0 do
//找到了一个文件或目录后
begin
//如果找到的是个目录
if (searchRec.Attr and faDirectory)<>0 then
begin
{在搜索非根目录(C:\、D:\)下的子目录时会出现'.','..'的"虚拟目录"
大概是表示上层目录和下层目录吧。。。要过滤掉才可以}
if (searchRec.Name <> '.') and (searchRec.Name <> '..') then
begin
{由于查找到的子目录只有个目录名,所以要添上上层目录的路径
searchRec.Name = 'Windows';tmpStr:='c:\Windows';
加个断点就一清二楚了}
tmpStr:=StrPas(path)+'\'+searchRec.Name;
//自身调用,查找子目录,递归。。。。
Result:=Result+EnumFileInRecursion(PChar(tmpStr));
end;
end
//如果找到的是个文件
{这个也是递归的结束条件,结束条件对于理解递归来说,相当重要}
else begin
{Result记录着搜索到的文件数。可是我是用CreateThread创建线程
来调用函数的,不知道怎么得到这个返回值。。。我不想用全局变量}
Result:=Result+1;
//把找到的文件加到Memo控件
Form1.Memo1.Lines.Add(StrPas(path)+'\'+searchRec.Name);
end;
//查找下一个文件或目录
found:=FindNext(searchRec);
end;
//释放资源
FindClose(searchRec);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
dwThreadID:Cardinal;
path:String;
begin
if hThreadHandle1 = 0 then
begin
Memo1.Lines.Clear;
//得到驱动盘的路径,这样可能啰嗦了些.
path:=DriveComboBox1.Drive+':';
//创建线程,使用递归方法遍历文件
hThreadHandle1:=CreateThread(nil,0,@EnumFileInRecursion,StrNew(PChar(path)),0,dwThreadID);
{@EnumFileInRecursion是函数的地址
PChar('c:')是函数的参数,以指针传递
不能直接使用@EnumFileInRecursion('c:')}
end else
begin
TerminateThread(hThreadHandle1,0);
CloseHandle(hThreadHandle1);
hThreadHandle1 := 0;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
//结束线程
if hThreadHandle1 <> 0 then
begin
TerminateThread(hThreadHandle1,0);
CloseHandle(hThreadHandle1);
hThreadHandle1 := 0;
end;
if hThreadHandle2 <> 0 then
begin
TerminateThread(hThreadHandle2,0);
CloseHandle(hThreadHandle2);
hThreadHandle2 := 0;
end;
end;
procedure TForm1.Button3Click(Sender: TObject);
var
dwThreadID:Cardinal;
path:String;
begin
if hThreadHandle2 = 0 then
begin
Memo1.Lines.Clear;
//得到驱动盘的路径,这样可能啰嗦了些.
path:=DriveComboBox1.Drive+':';
//创建线程,使用队列方法遍历文件
hThreadHandle2:=CreateThread(nil,0,@EnumFileInQueue,StrNew(PChar(path)),0,dwThreadID);
{@EnumFileInRecursion是函数的地址
PChar('c:')是函数的参数,以指针传递
不能直接使用@EnumFileInRecursion('c:')}
end
else begin
TerminateThread(hThreadHandle2,0);
CloseHandle(hThreadHandle2);
hThreadHandle2 := 0;
end;
end;
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
CanClose:=false;
//结束线程
if hThreadHandle1 <> 0 then
begin
TerminateThread(hThreadHandle1,0);
CloseHandle(hThreadHandle1);
hThreadHandle1 := 0;
end;
if hThreadHandle2 <> 0 then
begin
TerminateThread(hThreadHandle2,0);
CloseHandle(hThreadHandle2);
hThreadHandle2 := 0;
end;
CanClose:=true;
end;
end.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -