在文件里查找某一字符串,在程序设计上常常要用到。若用Delphi写,小文件可以用TmemoryStream把整个文件读进内存,在内存上进行查找。但若文件比较大,上几十兆,就不能用此法,若你你强行用TmemoryStream,硬盘因交换文件而不断的读写,效率很低。
大家对Win32的内存映射文件(File Mapping)一定不会陌生,用它可以方便的实现多个进程共享数据。这里介绍用内存映射文件作快速的全文检索,功能如Windows的查找(在某目录的文件里查找某一字符串),但此法比Windows的查找效率要高。
内存映射文件可以让我们在访问磁盘文件时如同正在访问内存中的文件一样,可以避免进行文件的输入输出操作。它先是保留一段虚拟内存地址空间,然后将磁盘文件提交给这段内存空间。我们只需要一个指向该区域的指针就可以访问整个文件的内容了。系统负责处理数据的缓存、缓冲、写入和调用以及内存的分配和释放,我们就好像在一块大的内存区域上查找字符串,效率比较高。
下面介绍具体的做法:
1、获得查找文件的文件句柄。可以用FileOpen()打开文件。
2、创建文件内存映射对象
无论是命名的或是无命名的内存映射文件对象,都是用CreateFileMapping()函数创建。函数的参数如下:
function CreateFileMapping(hFile: THandle; //OpenFile()返回的句柄
lpFileMappingAttributes: PSecurityAttributes;//安全属性,一般为nil
flProtect, {
PAGE_READONLY(文件只读)
PAGE_READWRITE (文件可读写)
PAGE_WRITECOPY(文件可读写,但进行写操作时,会复制修改过的页面)}
dwMaximumSizeHigh, //文件最大尺寸的高32位,除非文件大于4GB,否则为0
dwMaximumSizeLow: DWORD; //文件最大尺寸高32位
lpName: Pchar//文件映射对象的名称,可含除‘/’的所有字符,若nil创建无名对象
): THandle; stdcall;
3.映射文件视图到进程的地址空间
用MapViewOfFile()函数,函数参数:
function MapViewOfFile(hFileMappingObject: THandle; //CreateFileMapping()返回句柄
dwDesiredAccess: DWORD;//数据访问模式,可设读写、只读、Copy-on-write
dwFileOffsetHigh, dwFileOffsetLow,
dwNumberOfBytesToMap: DWORD //需要映射的字节数,0为文件全部
): Pointer; //返回视图的起初地址
4.在视图里查找字符串
本查找算法应用了回调函数,当找到符合的字符串时调用函数,若想停止查找,可返回False,详见源程序。
5.事后清洁工作
UnmapViewOfFile()解除文件视图,CloseHandle()分别关闭文件映射对象和文件内核对象。
下面是演示程序窗体和代码:
unit Unit1;interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TForm1 = class(TForm)
OpenDialog1: TOpenDialog;
GroupBox1: TGroupBox;
LFilename: TLabel;
EdSt: TEdit;
BBrowse: TButton;
BSearch: TButton;
ListBox1: TListBox;
Label2: TLabel;
Button3: TButton;
EdText: TEdit;
LText: TLabel;
Cbignore: TCheckBox;
procedure BBrowseClick(Sender: TObject);
procedure BSearchClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
type FindCallback=function(pos:integer):boolean;
var
Form1: TForm1;
implementation
{$R *.DFM}
//回调函数,pos为找到字符串在文件的偏移量,若返回True继续查找,False停止查找
function found(pos:integer):boolean;
begin
Form1.Listbox1.Items.Add('在偏移量:'+inttostr(pos)
+' 处找到此字符串');
result:=true;
end;
//查找函数,Fname为查找文件名,CallBack为回调函数(见下),ignoreCase为是否忽略
procedure searchText(Fname:string;text:pchar;
callback:FindCallback;ignoreCase:boolean=TRUE);
var
FFileHandle: THandle; // 文件内核句柄
FMapHandle: THandle; // 文件映射句柄
FFileSize: Integer;
PData: PChar; // 文件视图的地址
textlen,i:integer;
buf:array[0..255] of char;
begin
if not Assigned(callback) then exit;
//若CallBack过程是否有效,否退出
FFileHandle := FileOpen(FName, fmOpenRead);
//打开文件内核对象
if FFileHandle = INVALID_HANDLE_VALUE then
raise Exception.Create('打开文件错误');
try
FFileSize := GetFileSize(FFileHandle, Nil);
FMapHandle := CreateFileMapping(FFileHandle, nil,
PAGE_READONLY, 0, FFileSize, nil);
//只读方式创建文件内存映射对象
if FMapHandle = 0 then
raise Exception.Create('创建文件内存映射对象错误');
finally
CloseHandle(FFileHandle);
//若异常者关闭文件对象
end;
try
PData := MapViewOfFile(FMapHandle, FILE_MAP_READ, 0, 0,
FFileSize); //映射文件视图
//PData为映射文件视图,返回映射视图的初始地址
if PData = Nil then
raise Exception.Create('创建映射视图出错!');
finally
CloseHandle(FMapHandle); //关闭文件映射对象
end;
try
textlen:=strlen(text);
if ignorecase then //忽略大小写的处理
begin
strcopy(text,StrUpper(text));
for i:=0 to FFileSize-1-textlen do
begin
if UpCase(PData[i])=text[0] then
//找第一个吻合的字符
begin
move(Pdata[i+1],buf[0],textlen-1);
if strLcomp(StrUpper(buf),pchar(@text[1]),
textlen-1)=0 then //对比剩下的字符串
if not callback(i) //回传CallBack函数,若
then break; //返回False者退出查找
end;
end;
end else
begin //大小写区别的处理,和上面的处理相近
for i:=0 to FFileSize-1-textlen do
if PData[i]=text[0] then
begin
move(Pdata[i+1],buf[0],textlen-1);
if strLcomp(buf,pchar(@text[1]),
textlen-1)=0 then
if not callback(i) then break;
end;
end;
finally
UnmapViewOfFile(PData); //删除文件视图
end;
end;
procedure TForm1.BBrowseClick(Sender: TObject);
begin
if opendialog1.Execute then
edst.Text :=opendialog1.FileName;
end;
procedure TForm1.BSearchClick(Sender: TObject);
begin
searchText(Edst.text,pchar(edtext.text),found,Cbignore.checked);
end;
end.
|